Webiny-js webiny-http-route
install
source · Clone the upstream repo
git clone https://github.com/webiny/webiny-js
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/webiny/webiny-js "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/user-skills/api/http-route" ~/.claude/skills/webiny-webiny-js-webiny-http-route && rm -rf "$T"
manifest:
skills/user-skills/api/http-route/SKILL.mdsource content
Custom HTTP Routes
TL;DR
Add a custom HTTP route by implementing
Route.Interface and registering it with <Api.Route> in webiny.config.tsx. The path and method props configure API Gateway and Fastify route registration. Your handler receives a framework-agnostic Route.Request and Route.Reply and supports full DI (Logger, BuildParams, UseCases, etc.).
YOU MUST include the full file path with the
extension in the .ts
prop. For example, use src
src={"/extensions/MyRoute.ts"}, NOT src={"/extensions/MyRoute"}. Omitting the file extension will cause a build failure.
YOU MUST use
for the export default
call when the file is targeted directly by a createImplementation()
src prop. Named exports cause build failures here.
The Route Pattern
// extensions/MyRoute.ts import { Route } from "webiny/api/route"; import { Logger } from "webiny/api/logger"; import { BuildParams } from "webiny/api/build-params"; class MyRouteImpl implements Route.Interface { constructor( private logger: Logger.Interface, private buildParams: BuildParams.Interface ) {} async execute(request: Route.Request, reply: Route.Reply): Promise<void> { this.logger.info("Handling request", { url: request.url }); const config = this.buildParams.get<string>("MY_CONFIG"); reply.code(200).send({ status: "ok", config }); } } export default Route.createImplementation({ implementation: MyRouteImpl, dependencies: [Logger, BuildParams] });
Register in
webiny.config.tsx:
<Api.Route method={"POST"} path={"/my-route"} src={"/extensions/MyRoute.ts"} />
Props Reference
| Prop | Type | Required | Description |
|---|---|---|---|
| | Yes | Route path — must start with |
| | Yes | HTTP method (see list below) |
| | Yes | Path to the handler file (must include extension) |
| | No | Pulumi resource name (kebab-case). Auto-derived from path+method if omitted |
Supported HTTP Methods
DELETE, GET, HEAD, PATCH, POST, PUT, OPTIONS, ANY
Use
ANY to match all HTTP methods on a given path.
Route.Request / Route.Reply Interfaces
These are framework-agnostic — no Fastify types leak into user code.
Route.Request
Route.Requestinterface Route.Request { body: unknown; headers: Record<string, string | string[] | undefined>; method: string; url: string; params: unknown; // path parameters, e.g. { id: "abc" } query: unknown; // query string parameters }
Route.Reply
Route.Replyinterface Route.Reply { code(statusCode: number): this; // set HTTP status code send(data?: unknown): void; // send response body header(key: string, value: unknown): this; }
Chaining example:
reply.code(201).header("X-Custom", "value").send({ created: true });
How It Works
The
<Api.Route> extension does two things at build/deploy time:
-
Build time — injects two entries into
:apps/api/graphql/src/extensions.ts- A
that registers your handler in the DI containercreateContextPlugin - A
that registers the Fastify route with the hardcodedcreateRoute
andpathmethod
- A
-
Deploy time (Pulumi) — calls
on thegraphql.addRoute({ name, path, method })
module to create the API Gateway routeApiGraphql
At request time, the handler is resolved from the DI container — so all
dependencies (Logger, BuildParams, UseCases, etc.) are fully injected.
Example with Path Parameters
// extensions/GetOrderRoute.ts import { Route } from "webiny/api/route"; import { Logger } from "webiny/api/logger"; interface OrderParams { orderId: string; } class GetOrderRouteImpl implements Route.Interface { constructor(private logger: Logger.Interface) {} async execute(request: Route.Request, reply: Route.Reply): Promise<void> { const { orderId } = request.params as OrderParams; this.logger.info("Fetching order", { orderId }); // ... fetch and return order reply.code(200).send({ orderId, status: "fulfilled" }); } } export default Route.createImplementation({ implementation: GetOrderRouteImpl, dependencies: [Logger] });
<Api.Route method={"GET"} path={"/orders/{orderId}"} src={"/extensions/GetOrderRoute.ts"} />
Key Rules
andpath
are hardcoded at build time — the Fastify route is registered before any request arrives. Your handler does not need to specify them.method- DI is fully available in
— the handler instance is resolved from the container per request, so all dependencies are injected normally.execute() - One handler per file — each
file exports onesrc
result.Route.createImplementation - Constructor param order must match the
array exactly.dependencies - Use
extensions in all relative imports inside the handler file (ESM)..js - Do not read
at runtime — useprocess.env
for configuration instead.BuildParams
Quick Reference
Import (handler): import { Route } from "webiny/api/route"; Interface: Route.Interface Request type: Route.Request Reply type: Route.Reply Export: Route.createImplementation({ implementation, dependencies }) Register: <Api.Route method={"POST"} path={"/my-route"} src={"/extensions/MyRoute.ts"} /> Deploy: yarn webiny deploy api --env=dev
Related Skills
- webiny-api-architect — DI patterns, Services, UseCases, feature organization
- webiny-custom-graphql-api — Custom GraphQL endpoints (alternative to HTTP)
- webiny-dependency-injection — Injectable services catalog (Logger, BuildParams, etc.)
- webiny-infrastructure-extensions — Pulumi-level infrastructure customization