npx skills add https://github.com/encoredev/skills --skill encore-api使用 Encore.ts 创建 API 端点时,请遵循以下模式:
import { api } from "encore.dev/api";
始终为请求和响应类型定义明确的 TypeScript 接口:
interface CreateUserRequest {
email: string;
name: string;
}
interface CreateUserResponse {
id: string;
email: string;
name: string;
}
export const createUser = api(
{ method: "POST", path: "/users", expose: true },
async (req: CreateUserRequest): Promise<CreateUserResponse> => {
// 实现
}
);
| 选项 | 类型 | 描述 |
|---|---|---|
method |
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
| string |
| HTTP 方法:GET、POST、PUT、PATCH、DELETE |
path | string | URL 路径,支持 :param 和 *wildcard |
expose | boolean | 如果为 true,可从外部访问(默认:false) |
auth | boolean | 如果为 true,需要身份验证 |
sensitive | boolean | 如果为 true,从跟踪记录中隐藏请求/响应负载 |
Encore 支持四种端点配置:
// 既有请求也有响应
export const createUser = api(
{ method: "POST", path: "/users", expose: true },
async (req: CreateRequest): Promise<CreateResponse> => { ... }
);
// 仅响应(无请求体)
export const listUsers = api(
{ method: "GET", path: "/users", expose: true },
async (): Promise<ListResponse> => { ... }
);
// 仅请求(无响应体)
export const deleteUser = api(
{ method: "DELETE", path: "/users/:id", expose: true },
async (req: DeleteRequest): Promise<void> => { ... }
);
// 既无请求也无响应
export const ping = api(
{ method: "GET", path: "/ping", expose: true },
async (): Promise<void> => { ... }
);
在响应中包含 HttpStatus 字段以返回自定义状态码:
import { api, HttpStatus } from "encore.dev/api";
interface CreateResponse {
id: string;
status: HttpStatus;
}
export const create = api(
{ method: "POST", path: "/items", expose: true },
async (req: CreateRequest): Promise<CreateResponse> => {
const item = await createItem(req);
return { id: item.id, status: HttpStatus.Created }; // 返回 201
}
);
// 路径:"/users/:id"
interface GetUserRequest {
id: string; // 自动从 :id 映射
}
import { Query } from "encore.dev/api";
interface ListUsersRequest {
limit?: Query<number>;
offset?: Query<number>;
}
import { Header } from "encore.dev/api";
interface WebhookRequest {
signature: Header<"X-Webhook-Signature">;
payload: string;
}
import { Cookie } from "encore.dev/api";
interface SessionRequest {
session?: Cookie<"session">;
settings?: Cookie<"user-settings">;
}
Encore 在运行时使用 TypeScript 类型验证请求。添加约束以进行更严格的验证:
import { api } from "encore.dev/api";
import { Min, Max, MinLen, MaxLen, IsEmail, IsURL } from "encore.dev/validate";
interface CreateUserRequest {
email: string & IsEmail; // 必须是有效的电子邮件
username: string & MinLen<3> & MaxLen<20>; // 3-20 个字符
age: number & Min<13> & Max<120>; // 介于 13 和 120 之间
website?: string & IsURL; // 可选,如果提供则必须是 URL
}
使用 & 表示 AND 逻辑(必须通过所有规则),使用 | 表示 OR 逻辑(必须至少通过一个):
import { IsEmail, IsURL, MinLen, MaxLen } from "encore.dev/validate";
interface ContactRequest {
// 必须是有效的电子邮件 OR 有效的 URL
contact: string & (IsEmail | IsURL);
// 必须是 5-100 个字符 AND 是有效的 URL
website: string & MinLen<5> & MaxLen<100> & IsURL;
}
| 验证器 | 适用于 | 示例 |
|---|---|---|
Min<N> | number | age: number & Min<18> |
Max<N> | number | count: number & Max<100> |
MinLen<N> | string, array | name: string & MinLen<1> |
MaxLen<N> | string, array | tags: string[] & MaxLen<10> |
IsEmail | string | email: string & IsEmail |
IsURL | string | link: string & IsURL |
StartsWith<S> | string | id: string & StartsWith<"usr_"> |
EndsWith<S> | string | file: string & EndsWith<".json"> |
MatchesRegexp<R> | string | code: string & MatchesRegexp<"^[A-Z]{3}$"> |
无效请求返回 400 并包含详细信息:
{
"code": "invalid_argument",
"message": "validation failed",
"details": { "field": "email", "error": "must be a valid email" }
}
对于 Webhook 或需要直接访问请求/响应的情况,使用 api.raw:
export const stripeWebhook = api.raw(
{ expose: true, path: "/webhooks/stripe", method: "POST" },
async (req, res) => {
const sig = req.headers["stripe-signature"];
// 处理原始请求...
res.writeHead(200);
res.end();
}
);
使用 APIError 返回正确的 HTTP 错误响应:
import { APIError, ErrCode } from "encore.dev/api";
// 使用错误代码抛出
throw new APIError(ErrCode.NotFound, "user not found");
// 或使用简写
throw APIError.notFound("user not found");
throw APIError.invalidArgument("email is required");
throw APIError.unauthenticated("invalid token");
| 代码 | HTTP 状态 | 用途 |
|---|---|---|
NotFound | 404 | 资源不存在 |
InvalidArgument | 400 | 输入错误 |
Unauthenticated | 401 | 缺少/无效的身份验证 |
PermissionDenied | 403 | 不允许 |
AlreadyExists | 409 | 重复资源 |
使用 api.static 提供静态文件(HTML、CSS、JS、图像):
import { api } from "encore.dev/api";
// 在 /static/* 下提供 ./assets 中的文件
export const assets = api.static(
{ expose: true, path: "/static/*path", dir: "./assets" }
);
// 在根路径提供服务(使用 !path 进行后备路由)
export const frontend = api.static(
{ expose: true, path: "/!path", dir: "./dist" }
);
// 自定义 404 页面
export const app = api.static(
{ expose: true, path: "/!path", dir: "./public", notFound: "./404.html" }
);
*path - 标准通配符:匹配前缀下的所有路径(例如,/static/*path)!path - 后备路由:在域根路径提供静态文件,而不与其他 API 端点冲突。对于未匹配的路由应提供 index.html 的单页应用(SPA),请使用此语法import 而不是 requireexpose: trueapi.raw,其他情况使用 apiAPIError 而不是返回错误对象Min、MaxLen 等)每周安装数
171
仓库
GitHub 星标数
20
首次出现
2026年1月21日
安全审计
安装于
codex134
opencode130
gemini-cli128
github-copilot112
claude-code112
cursor102
When creating API endpoints with Encore.ts, follow these patterns:
import { api } from "encore.dev/api";
Always define explicit TypeScript interfaces for request and response types:
interface CreateUserRequest {
email: string;
name: string;
}
interface CreateUserResponse {
id: string;
email: string;
name: string;
}
export const createUser = api(
{ method: "POST", path: "/users", expose: true },
async (req: CreateUserRequest): Promise<CreateUserResponse> => {
// Implementation
}
);
| Option | Type | Description |
|---|---|---|
method | string | HTTP method: GET, POST, PUT, PATCH, DELETE |
path | string | URL path, supports :param and *wildcard |
expose | boolean | If true, accessible from outside (default: false) |
auth | boolean | If true, requires authentication |
sensitive | boolean | If true, redacts request/response payloads from traces |
Encore supports four endpoint configurations:
// Both request and response
export const createUser = api(
{ method: "POST", path: "/users", expose: true },
async (req: CreateRequest): Promise<CreateResponse> => { ... }
);
// Response only (no request body)
export const listUsers = api(
{ method: "GET", path: "/users", expose: true },
async (): Promise<ListResponse> => { ... }
);
// Request only (no response body)
export const deleteUser = api(
{ method: "DELETE", path: "/users/:id", expose: true },
async (req: DeleteRequest): Promise<void> => { ... }
);
// Neither request nor response
export const ping = api(
{ method: "GET", path: "/ping", expose: true },
async (): Promise<void> => { ... }
);
Include an HttpStatus field in your response to return custom status codes:
import { api, HttpStatus } from "encore.dev/api";
interface CreateResponse {
id: string;
status: HttpStatus;
}
export const create = api(
{ method: "POST", path: "/items", expose: true },
async (req: CreateRequest): Promise<CreateResponse> => {
const item = await createItem(req);
return { id: item.id, status: HttpStatus.Created }; // Returns 201
}
);
// Path: "/users/:id"
interface GetUserRequest {
id: string; // Automatically mapped from :id
}
import { Query } from "encore.dev/api";
interface ListUsersRequest {
limit?: Query<number>;
offset?: Query<number>;
}
import { Header } from "encore.dev/api";
interface WebhookRequest {
signature: Header<"X-Webhook-Signature">;
payload: string;
}
import { Cookie } from "encore.dev/api";
interface SessionRequest {
session?: Cookie<"session">;
settings?: Cookie<"user-settings">;
}
Encore validates requests at runtime using TypeScript types. Add constraints for stricter validation:
import { api } from "encore.dev/api";
import { Min, Max, MinLen, MaxLen, IsEmail, IsURL } from "encore.dev/validate";
interface CreateUserRequest {
email: string & IsEmail; // Must be valid email
username: string & MinLen<3> & MaxLen<20>; // 3-20 characters
age: number & Min<13> & Max<120>; // Between 13 and 120
website?: string & IsURL; // Optional, must be URL if provided
}
Use & for AND logic (must pass all rules) and | for OR logic (must pass at least one):
import { IsEmail, IsURL, MinLen, MaxLen } from "encore.dev/validate";
interface ContactRequest {
// Must be valid email OR valid URL
contact: string & (IsEmail | IsURL);
// Must be 5-100 chars AND be a valid URL
website: string & MinLen<5> & MaxLen<100> & IsURL;
}
| Validator | Applies To | Example |
|---|---|---|
Min<N> | number | age: number & Min<18> |
Max<N> | number | count: number & Max<100> |
MinLen<N> | string, array | name: string & MinLen<1> |
MaxLen<N> |
Invalid requests return 400 with details:
{
"code": "invalid_argument",
"message": "validation failed",
"details": { "field": "email", "error": "must be a valid email" }
}
Use api.raw for webhooks or when you need direct request/response access:
export const stripeWebhook = api.raw(
{ expose: true, path: "/webhooks/stripe", method: "POST" },
async (req, res) => {
const sig = req.headers["stripe-signature"];
// Handle raw request...
res.writeHead(200);
res.end();
}
);
Use APIError for proper HTTP error responses:
import { APIError, ErrCode } from "encore.dev/api";
// Throw with error code
throw new APIError(ErrCode.NotFound, "user not found");
// Or use shorthand
throw APIError.notFound("user not found");
throw APIError.invalidArgument("email is required");
throw APIError.unauthenticated("invalid token");
| Code | HTTP Status | Usage |
|---|---|---|
NotFound | 404 | Resource doesn't exist |
InvalidArgument | 400 | Bad input |
Unauthenticated | 401 | Missing/invalid auth |
PermissionDenied | 403 | Not allowed |
AlreadyExists | 409 | Duplicate resource |
Serve static files (HTML, CSS, JS, images) with api.static:
import { api } from "encore.dev/api";
// Serve files from ./assets under /static/*
export const assets = api.static(
{ expose: true, path: "/static/*path", dir: "./assets" }
);
// Serve at root (use !path for fallback routing)
export const frontend = api.static(
{ expose: true, path: "/!path", dir: "./dist" }
);
// Custom 404 page
export const app = api.static(
{ expose: true, path: "/!path", dir: "./public", notFound: "./404.html" }
);
*path - Standard wildcard: matches all paths under the prefix (e.g., /static/*path)!path - Fallback routing: serves static files at domain root without conflicting with other API endpoints. Use this for SPAs where unmatched routes should serve index.htmlimport not requireexpose: true only for public endpointsapi.raw for webhooks, api for everything elseAPIError instead of returning error objectsMin, MaxLen, etc.) for user inputWeekly Installs
171
Repository
GitHub Stars
20
First Seen
Jan 21, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
codex134
opencode130
gemini-cli128
github-copilot112
claude-code112
cursor102
agent-browser 浏览器自动化工具 - Vercel Labs 命令行网页操作与测试
157,400 周安装
| string, array |
tags: string[] & MaxLen<10> |
IsEmail | string | email: string & IsEmail |
IsURL | string | link: string & IsURL |
StartsWith<S> | string | id: string & StartsWith<"usr_"> |
EndsWith<S> | string | file: string & EndsWith<".json"> |
MatchesRegexp<R> | string | code: string & MatchesRegexp<"^[A-Z]{3}$"> |