grill-me
Interview the user relentlessly about a plan or design until reaching shared understanding,…
Adding custom HTTP routes to the API with <Api.Route> and HttpRouteHandler. Use this skill when the developer wants to expose a custom HTTP endpoint (GET, POST, PUT, etc.) on the API alongside the GraphQL handler, implement a route handler with full DI support, or register a
$ npx -y skills add webiny/webiny-js --skill http-route --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/http-routeContext preview
The summary Claude sees to decide when to auto-load this skill.
Adding custom HTTP routes to the API with <Api.Route> and HttpRouteHandler. Use this skill when the developer wants to expose a custom HTTP endpoint (GET, POST, PUT, etc.) on the API alongside the GraphQL handler, implement a route handler with full DI support, or register a
name: webiny-http-route description: > Adding custom HTTP routes to the API with <Api.Route> and HttpRouteHandler. Use this skill when the developer wants to expose a custom HTTP endpoint (GET, POST, PUT, etc.) on the API alongside the GraphQL handler, implement a route handler with full DI support, or register a custom HTTP route in webiny.config.tsx.
Write a handler that implements `HttpRouteHandler.Interface`, then point `<Api.Route>` at it in `webiny.config.tsx`. The `method` and `path` props configure both the API Gateway route and the router, so the handler file never restates them. Handlers get full DI.
**YOU MUST include the full file path with the `.ts` extension in the `src` prop.** Use `src={"/extensions/MyRoute.ts"}`, not `src={"/extensions/MyRoute"}`. Omitting it fails the build.
**YOU MUST use `export default`** for the `createImplementation()` call. Named exports fail here.
// extensions/MyRoute.ts
import { HttpRouteHandler, Logger } from "webiny/api";
class MyRouteImpl implements HttpRouteHandler.Interface {
constructor(private logger: Logger.Interface) {}
async handle(request: HttpRouteHandler.Request, response: HttpRouteHandler.Response) {
this.logger.info({ path: request.path }, "Handling request");
return response.status(200).json({ status: "ok" });
}
}
export default HttpRouteHandler.createImplementation({
implementation: MyRouteImpl,
dependencies: [Logger]
});Register it:
<Api.Route method={"POST"} path={"/my-route"} src={"/extensions/MyRoute.ts"} />| Prop | Type | Required | Description | | ----------- | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------- | | `path` | `string` | Yes | Route path — must start with `/` | | `method` | `string` | Yes | HTTP method (see below) | | `src` | `string` | Yes | Path to the handler file (must include `.ts`) | | `routeName` | `string` | No | Route name (kebab-case). Derived from path + method if omitted. Doubles as the Pulumi resource name and the id a decorator matches on |
Methods: `DELETE`, `GET`, `HEAD`, `PATCH`, `POST`, `PUT`, `OPTIONS`, `ANY`. Use `ANY` to match every method on a path.
Write them either way. `{orderId}` is API Gateway syntax, `:orderId` is the router's; the extension converts to whichever each consumer needs, so both work and mean the same thing.
<Api.Route method={"GET"} path={"/orders/{orderId}"} src={"/extensions/GetOrderRoute.ts"} />
<Api.Route method={"GET"} path={"/orders/:orderId"} src={"/extensions/GetOrderRoute.ts"} />Read them off `request.pathParameters`:
class GetOrderRouteImpl implements HttpRouteHandler.Interface {
constructor(private getOrder: GetOrderUseCase.Interface) {}
async handle(request: HttpRouteHandler.Request, response: HttpRouteHandler.Response) {
const order = await this.getOrder.execute(request.pathParameters.orderId);
return response.status(200).json(order);
}
}
export default HttpRouteHandler.createImplementation({
implementation: GetOrderRouteImpl,
dependencies: [GetOrderUseCase]
});Wildcards are the exception: `/files/*` matches for the router, `{proxy+}` for API Gateway, and they capture differently. Write a wildcard route for the target you mean.
`HttpRouteHandler.Request` is transport-agnostic — no API Gateway or Node types leak into your code.
interface Request {
method: string;
path: string;
headers: Record<string, string>;
query: Record<string, string>;
pathParameters: Record<string, string>;
body: any;
/** Which route matched — `{ name, method, path }`. */
route: MatchedRouteDefinition;
}`route` is what makes a decorator able to act on one route (see below), and lets a handler read its own identity. `method`/`path` on it are the route's PATTERN (`/orders/:orderId`), where the top-level `method`/`path` are the request's actual values (`/orders/abc123`).
`HttpRouteHandler.Response` is a mutable builder, the `res` of an Express-style handler. Every method returns `this`, so calls chain:
| Method | Purpose | | ------------------------------- | ------------------------------------- | | `status(code)` | Set the status code (defaults to 200) | | `json(body)` | JSON body + content type | | `text(body)` | Plain-text body | | `send(body)` | Body as-is | | `header(name, value)` | Set one header | | `getHeader(name)` | Read a header already set | | `cookie(name, value, options?)` | Set a cookie | | `clearCookie(name, options?)` | Expire a cookie | | `redirect(url, statusCode?)` | Redirect | | `sse(source)` | Server-sent events stream |
return response.status(201).cookie("sid", id, { httpOnly: true }).json({ id });Returning the builder is optional — mutate it and return nothing for the same result. Returning a plain object works too; anything set on the builder is merged underneath it, and the returned object wins on conflicts.
`cookie`'s `maxAge` is in **seconds** (the `M
Open-source content platform. Self-hosted on AWS serverless. Built as a TypeScript framework you extend with code, not a closed product you configure through a UI. Runs on Lambda, DynamoDB, S3, and CloudFront inside your own AWS account. Scales automatically.
Repo: webiny/webiny-js
Interview the user relentlessly about a plan or design until reaching shared understanding,…
Turn a PRD into a multi-phase implementation plan using tracer-bullet vertical slices, saved…
Webiny-only. Run all checks required before packages are ready for publish: deps, build,…
Use when running tests. Shows how to run tests for a single package, including OpenSearch…
Generate, refresh, and maintain Webiny MCP server skills from source documentation and…
Create a PRD through user interview, codebase exploration, and module design, then submit as…