Skip to content
Content
Skill

/http-route

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

BOOST
From plugin
webiny-js
8k76 skills3 MCP
Install
$ npx -y skills add webiny/webiny-js --skill http-route --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/http-route

Context 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

SKILL.md

http-route.SKILL.md
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.

Custom HTTP Routes

TL;DR

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.

The route pattern

// 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"} />

Props reference

| 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.

Path parameters

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.

Request

`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`).

Response

`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

Read more
Ships withwebiny-js

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.

Get the whole plugin
Stats
8,048
Stars
682
Forks
Active
Maintenance
TypeScript
Language
2h ago
Last commit
8y ago
Created
9h ago
Added

Repo: webiny/webiny-js

Other skills on webiny-js.