Agent Skills › webiny/webiny-js › webiny-http-route

webiny-http-route

GitHub

指导在Webiny中通过Api.Route组件注册自定义HTTP路由,实现Handler类并配置路径、方法及依赖注入。

skills/user-skills/api/http-route/SKILL.md webiny/webiny-js

Trigger Scenarios

需要添加新的HTTP API端点 配置自定义路由处理器 设置API Gateway路由映射

Install

npx skills add webiny/webiny-js --skill webiny-http-route -g -y
More Options

Non-standard path

npx skills add https://github.com/webiny/webiny-js/tree/next/skills/user-skills/api/http-route -g -y

Use without installing

npx skills use webiny/webiny-js@webiny-http-route

指定 Agent (Claude Code)

npx skills add webiny/webiny-js --skill webiny-http-route -a claude-code -g -y

安装 repo 全部 skill

npx skills add webiny/webiny-js --all -g -y

预览 repo 内 skill

npx skills add webiny/webiny-js --list

SKILL.md

Frontmatter
{
    "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 Max-Age attribute), unlike Express, which uses milliseconds.

How it works

<Api.Route> does two things:

  1. Build time — writes a registration into apps/api/graphql/src/extensions.ts that registers an HttpRouteDefinition built from your method and path, pointing at your handler.
  2. Deploy time — calls addRoute({ name, path, method }) on the API Pulumi module to create the API Gateway route, with the path converted to {param} syntax.

At request time the router matches the definition (cheap — it holds only method, path and the handler class) and builds your handler only then, with its dependencies injected. A route your request didn't match is never constructed.

Decorating a route

Two hooks, both ordinary DI decorators.

Change what a route DOES

Decorate HttpRouteHandler. It applies to every route, and request.route.name picks the one you mean:

import { HttpRouteHandler } from "webiny/api";

export default HttpRouteHandler.createDecorator({
  decorator: class implements HttpRouteHandler.Interface {
    constructor(private decoratee: HttpRouteHandler.Interface) {}

    async handle(request: HttpRouteHandler.Request, response: HttpRouteHandler.Response) {
      if (request.route.name !== "my-route-get") {
        return this.decoratee.handle(request, response);
      }

      if (request.headers["x-api-key"] !== "expected") {
        return response.status(401).json({ message: "Not authorized." });
      }

      return this.decoratee.handle(request, response);
    }
  },
  dependencies: []
});

Drop the name check and it wraps every route, which is what you want for timing or logging.

request.route is { name, method, path } of the matched route. name is routeName, or the value derived from path and method (/my-route + GET → my-route-get). A route can read it to find out its own identity too.

Change what a route IS

Decorate HttpRouteDefinition when you need to alter the route itself — point it at a different handler, or move its path:

import { HttpRouteDefinition } from "webiny/api";

export default HttpRouteDefinition.createDecorator({
  decorator: class implements HttpRouteDefinition.Interface {
    readonly name: string;
    readonly method: string;
    readonly path: string;
    readonly handler: HttpRouteDefinition.Interface["handler"];

    constructor(decoratee: HttpRouteDefinition.Interface) {
      this.name = decoratee.name;
      this.method = decoratee.method;
      this.path = decoratee.name === "my-route-get" ? "/moved" : decoratee.path;
      this.handler = decoratee.handler;
    }
  },
  dependencies: []
});

A decorator exposes the same properties as what it wraps, so it copies through the ones it doesn't change. Getters work too if you prefer them — these are plain properties, not methods.

Key rules

  • Do not declare method/path in the handler file. They come from the props. A handler that sets its own would be ignored, and the two could disagree.
  • Export the handler, not a definition. <Api.Route> builds the definition for you.
  • Constructor parameter order must match the dependencies array exactly.
  • Declare dependencies; don't inject the container and resolve inside handle().
  • One handler per file — each src file default-exports one implementation.
  • Use .js extensions in relative imports (ESM).
  • Do not read process.env at runtime; use BuildParams.
  • No console.* in API code — inject Logger.

Quick reference

Import:      import { HttpRouteHandler } from "webiny/api";
Interface:   HttpRouteHandler.Interface
Request:     HttpRouteHandler.Request
Response:    HttpRouteHandler.Response
Export:      export default HttpRouteHandler.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, use cases, 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

Version History

  • ef3b8bf Current 2026-09-22 23:46

    修复Api.Route可达性问题并支持两种路径语法;为路由命名以支持装饰器匹配。

  • 80eb1c5 2026-08-20 10:07

Same Skill Collection

.claude/skills/grill-me/SKILL.md
.claude/skills/prd-to-plan/SKILL.md
.claude/skills/preflight/SKILL.md
.claude/skills/tester/SKILL.md
.claude/skills/write-a-prd/SKILL.md
skills/repo-skills/add-feature-flag/SKILL.md
skills/user-skills/admin/admin-architect/SKILL.md
skills/user-skills/admin/admin-permissions/SKILL.md
skills/user-skills/admin/form-model/SKILL.md
skills/user-skills/admin/new-entry-wizard/SKILL.md
skills/user-skills/admin/website-builder/page-settings/SKILL.md
skills/user-skills/admin/website-builder/wb-preview-url-modifier/SKILL.md
skills/user-skills/api-bundle-size-limit/SKILL.md
skills/user-skills/api/api-architect/SKILL.md
skills/user-skills/api/cms-bulk-actions/SKILL.md
skills/user-skills/api/custom-field-type/SKILL.md
skills/user-skills/api/event-handler-pattern/SKILL.md
skills/user-skills/api/graphql-api/SKILL.md
skills/user-skills/api/permissions/SKILL.md
skills/user-skills/api/use-case-pattern/SKILL.md
skills/user-skills/api/v5-to-v6-migration/SKILL.md
skills/user-skills/api/websocket-notifications/SKILL.md
skills/user-skills/cli-extensions/SKILL.md
skills/user-skills/configure-auth0/SKILL.md
skills/user-skills/configure-entraid/SKILL.md
skills/user-skills/configure-okta/SKILL.md
skills/user-skills/dependency-injection/SKILL.md
skills/user-skills/full-stack-architect/SKILL.md
skills/user-skills/generated/api/aco/SKILL.md
skills/user-skills/generated/api/cms/SKILL.md
skills/user-skills/generated/api/file-manager/SKILL.md
skills/user-skills/generated/api/scheduler/SKILL.md
skills/user-skills/generated/api/security/SKILL.md
skills/user-skills/generated/api/system/SKILL.md
skills/user-skills/generated/api/tenancy/SKILL.md
skills/user-skills/generated/api/tenant-manager/SKILL.md
skills/user-skills/generated/api/website-builder/SKILL.md
skills/user-skills/generated/infra/SKILL.md
skills/user-skills/infrastructure-extensions/SKILL.md
skills/user-skills/local-development/SKILL.md
skills/user-skills/mailer-smtp/SKILL.md
skills/user-skills/project-structure/SKILL.md
.claude/skills/webiny-skill-creator/SKILL.md
skills/user-skills/admin/ui-extensions/SKILL.md
skills/user-skills/api/ai-powerups-content/SKILL.md
skills/user-skills/cognito-federation/SKILL.md
skills/user-skills/content-models/SKILL.md
skills/user-skills/generated/admin/aco/SKILL.md
skills/user-skills/generated/admin/ai-powerups/SKILL.md

Metadata

Files
0
Version
ef3b8bf
Hash
e219898e
Indexed
2026-08-20 10:07

trang chủ - Wiki
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-09-25 18:01
浙ICP备14020137号-1