mcpField
E
encore-api

encore-api

github.com/encoredevDevelopment30843 installsUpdated 2d ago
Repository

About encore-api

encore-api is a skill guide for building typed API endpoints with Encore.ts. It is for developers defining HTTP routes, request validation, error handling, and static asset serving in TypeScript.

Key features

  1. 01Define typed HTTP endpoints
  2. 02Map path, query, header, and cookie inputs
  3. 03Validate requests with constraints
  4. 04Return HTTP errors with APIError
  5. 05Serve static assets and SPA fallbacks

Use cases

  1. 01Build typed CRUD endpoints
  2. 02Validate incoming user input
  3. 03Handle authentication and resource errors
  4. 04Serve static files for an application

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/encoredev/skills --skill encore-api

After installing, type/encore-apiin Claude Code to confirm it loaded.

Details

From the project README

Encore API Endpoints

Instructions

When creating API endpoints with Encore.ts, follow these patterns:

1. Import the API module
import { api } from "encore.dev/api";
2. Define typed request/response interfaces

Always define explicit TypeScript interfaces for request and response types:

interface CreateUserRequest {
  email: string;
  name: string;
}

interface CreateUserResponse {
  id: string;
  email: string;
  name: string;
}
3. Create the endpoint
export const createUser = api(
  { method: "POST", path: "/users", expose: true },
  async (req: CreateUserRequest): Promise<CreateUserResponse> => {
    // Implementation
  }
);

API Options

OptionTypeDescription
methodstringHTTP method: GET, POST, PUT, PATCH, DELETE
pathstringURL path, supports :param and *wildcard
exposebooleanIf true, accessible from outside (default: false)
authbooleanIf true, requires authentication
sensitivebooleanIf true, redacts request/response payloads from traces

Request/Response Patterns

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> => { ... }
);

Custom HTTP Status Codes

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
  }
);

Parameter Types

Path Parameters
// Path: "/users/:id"
interface GetUserRequest {
  id: string;  // Automatically mapped from :id
}
Query Parameters
import { Query } from "encore.dev/api";

interface ListUsersRequest {
  limit?: Query<number>;
  offset?: Query<number>;
}
Headers
import { Header } from "encore.dev/api";

interface WebhookRequest {
  signature: Header<"X-Webhook-Signature">;
  payload: string;
}
Cookies
import { Cookie } from "encore.dev/api";

interface SessionRequest {
  session?: Cookie<"session">;
  settings?: Cookie<"user-settings">;
}

Request Validation

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
}
Combining Validation Rules

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;
}
Available Validators
ValidatorApplies ToExample
Min<N>numberage: number & Min<18>
Max<N>numbercount: number & Max<100>
MinLen<N>string, arrayname: string & MinLen<1>
MaxLen<N>string, arraytags: string[] & MaxLen<10>
IsEmailstringemail: string & IsEmail
IsURLstringlink: string & IsURL
StartsWith<S>stringid: string & StartsWith<"usr_">
EndsWith<S>stringfile: string & EndsWith<".json">
MatchesRegexp<R>stringcode: string & MatchesRegexp<"^[A-Z]{3}$">
Validation Error Response

Invalid requests return 400 with details:

{
  "code": "invalid_argument",
  "message": "validation failed",
  "details": { "field": "email", "error": "must be a valid email" }
}

Error Handling

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");

Common Error Codes

CodeHTTP StatusUsage
NotFound404Resource doesn't exist
InvalidArgument400Bad input
Unauthenticated401Missing/invalid auth
PermissionDenied403Not allowed
AlreadyExists409Duplicate resource

Static Assets

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 Syntax
  • *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.html

Guidelines

  • Always use import not require
  • Define explicit interfaces for type safety
  • Use expose: true only for public endpoints
  • Throw APIError instead of returning error objects
  • For inbound webhooks (Stripe, GitHub, etc.) use api.raw — see the encore-webhook skill
  • Path parameters are automatically extracted from the path pattern
  • Use validation constraints (Min, MaxLen, etc.) for user input

Comments

Markdown supported