Skip to main content

stacks-api

Use when building, modifying, or debugging API endpoints in a Stacks application - defining routes, handling requests, API middleware, working with the API server, HTTP client (fetcher), API resources, or OpenAPI generation. Covers both @stacksjs/api utilities and the stacks-api server implementation.

소스 정보

저장소
stacksjs/stacks
최근 소스 활동
2026년 10월 6일 20:01
감지된 SKILL.md 언어
영어
스타
631
포크
20

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
stacks-api
description
Use when building, modifying, or debugging API endpoints in a Stacks application - defining routes, handling requests, API middleware, working with the API server, HTTP client (fetcher), API resources, or OpenAPI generation. Covers both @stacksjs/api utilities and the stacks-api server implementation.
license
MIT
compatibility
Bun >= 1.3.0, TypeScript
allowed-tools
Read Edit Write Bash Grep Glob
# Stacks API The Stacks API system comprises two packages: the `@stacksjs/api` utilities package (HTTP client, API resources, OpenAPI generation) and the `stacks-api` server (Bun HTTP server with hot-reload). Routing is delegated to `@stacksjs/router` which wraps `@stacksjs/bun-router`. ## Key Paths - Core utilities (package): `storage/framework/core/api/src/` - API server: `storage/framework/api/` - Router package: `storage/framework/core/router/src/` - Route definitions: `routes/` - Route registry: `app/Routes.ts` (re-exports `storage/framework/defaults/app/Routes.ts`) - Port configuration: `config/ports.ts` - Generated OpenAPI spec: `storage/framework/api/openapi.json` - Generated API types: `storage/framework/api/api-types.ts` - Actions directory: `app/Actions/` - Controllers directory: `app/Controllers/` - Middleware directory: `app/Middleware/` - Middleware alias map: `app/Middleware.ts` - Package: `@stacksjs/api` ## Source Files ``` core/api/src/ ├── index.ts # Re-exports fetcher, generate-openapi, resource ├── fetcher.ts # Fetcher HTTP client class (fluent API) ├── generate-openapi.ts # OpenAPI 3.0 spec generator from route definitions ├── resource.ts # JsonResource, ResourceCollection, PaginatedResourceCollection └── types.ts # FetcherResponse, QueryParams, BodyData, FileAttachment framework/api/ ├── dev.ts # API server entry point (Bun --hot, file watchers) ├── build.ts # Bun.build config for production ├── api-types.ts # Auto-generated TypeScript types from OpenAPI spec ├── openapi.json # Generated OpenAPI 3.0 specification ├── package.json # Dependencies: @stacksjs/router, queue, storage, logging └── tsconfig.json core/router/src/ ├── index.ts # Re-exports bun-router + stacks-specific additions ├── stacks-router.ts # Route object, serve(), url(), action/controller resolution ├── response.ts # Response factory (json, text, html, redirect, etc.) ├── middleware.ts # Middleware class definition ├── request-context.ts # AsyncLocalStorage-based request context (request proxy) ├── route-loader.ts # Loads route files from registry with auto-prefixing ├── error-handler.ts # Ignition-style error pages, query tracking └── action-paths.ts # StacksActionPath type definitions routes/ ├── api.ts # Main API routes (auto-prefixed with /api; see #1835) ├── v1.ts # Versioned routes (loaded at /v1/*) ├── buddy.ts # Buddy CLI routes (loaded at /buddy/*) └── users.ts # Example user routes (commented out) ``` ## API Server (dev.ts) The API server runs on Bun's HTTP server with hot-reload (`bun --hot run dev.ts`). ```typescript import { serve } from '@stacksjs/router' serve({ port: ports.api || 3008, // Configurable via config/ports.ts or PORT_API env timezone: app.timezone || 'UTC', }) ``` ### Hot Reload Watchers The dev server watches these directories for changes and auto-reloads: - `storage/framework/core/*/src/` -- rebuilds core packages on change - `routes/` -- detects route file changes - `app/Actions/` -- invalidates module cache, hot-reloads actions - `app/Controllers/` -- invalidates module cache, hot-reloads controllers - `app/Middleware/` -- invalidates module cache, hot-reloads middleware - `app/Models/` (user models) -- regenerates the model artifacts (there is no `generate:model-files` command; the dev server does this itself, and `buddy generate:types` is the manual equivalent) Production build: `bun build.ts` bundles `dev.ts` to `dist/dev.js`. ## Route Definitions ### Route Registry (app/Routes.ts) Routes are organized in `routes/*.ts` files and registered in `app/Routes.ts`: ```typescript export interface RouteDefinition { path: string // Route file path relative to routes/ prefix?: string // URL prefix (overrides key-based prefix) middleware?: string | string[] // Middleware for all routes in file } export type RouteRegistry = Record<string, string | RouteDefinition> export default { 'api': 'api', // Auto-prefixed with /api (see #1835) 'v1': { path: 'v1', prefix: 'v1' }, // routes at /v1/* // 'admin': { path: 'admin', middleware: ['auth'] }, } satisfies RouteRegistry ``` `'web'` is the only key that loads at root `/` with no prefix; all other keys (including `'api'`) auto-prefix with `/<key>`. The `'api'` auto-prefix lines up with the rpx proxy forward path so a user route registered as `route.get('/cart/add', ...)` in `routes/api.ts` resolves at `https://<domain>/api/cart/add` through the dev proxy. ### Route Methods ```typescript import { route, response } from '@stacksjs/router' // Inline handler route.get('/hello', () => response.json({ message: 'Hello' })) route.post('/users', () => response.created({ id: 1 })) // String-based handler (Action) route.get('/subscribers', 'Actions/SubscriberIndexOrmAction') route.post('/login', 'Actions/Auth/LoginAction') // String-based handler (Controller@method) route.get('/coming-soon', 'Controllers/ComingSoonController@index') route.get('/stats', 'Controllers/QueryController@getStats') // Route with path parameters route.get('/foo/bar/{id}', () => response.text('hello')) route.get('/users/{id}/posts/{postId}', handler) // Route chaining: middleware + naming route.get('/tokens', 'Actions/Auth/ListTokensAction').middleware('auth') route.post('/email/subscribe', 'Actions/SubscriberEmailAction').name('email.subscribe') route.delete('/tokens/{id}', 'Actions/Auth/RevokeTokenAction').middleware('auth').name('token.revoke') // Route groups (prefix + middleware) route.group({ prefix: '/auth' }, () => { route.post('/refresh', 'Actions/Auth/RefreshTokenAction') route.get('/tokens', 'Actions/Auth/ListTokensAction').middleware('auth') }) route.group({ middleware: 'auth' }, () => { route.get('/me', 'Actions/Auth/AuthUserAction') route.post('/logout', 'Actions/Auth/LogoutAction') }) // Health check (adds GET /health) route.health() ``` ### Named Routes & URL Generation ```typescript import { url } from '@stacksjs/router' // Define named routes route.get('/api/email/unsubscribe', 'Actions/UnsubscribeAction').name('email.unsubscribe') route.get('/users/{id}/posts/{postId}', handler).name('user.post') // Generate URLs url('email.unsubscribe', { token: 'abc-123' }) // => https://stacksjs.com/api/email/unsubscribe?token=abc-123 url('user.post', { id: 42, postId: 7 }) // => https://stacksjs.com/users/42/posts/7 ``` Path parameters (`{id}`) are substituted; remaining params become query string. Uses `APP_URL` env var as base (defaults to `https://localhost`). ## Action Resolution String-based route handlers are resolved at runtime: 1. **Actions** (`'Actions/MyAction'`): checks `app/Actions/MyAction.ts` first, falls back to `storage/framework/defaults/app/Actions/MyAction.ts` 2. **ORM Actions** (`'Actions/UserIndexOrmAction'`): loaded from `storage/framework/actions/src/` 3. **Controllers** (`'Controllers/MyController@method'`): checks `app/Controllers/` first, falls back to defaults Action modules must export a default object with a `handle(req: EnhancedRequest)` method: ```typescript // app/Actions/MyAction.ts export default { handle(request: EnhancedRequest): Response { const name = request.get('name', 'world') return Response.json({ message: `Hello ${name}` }) } } ``` Actions can also define `validations` for automatic input validation: ```typescript export default { validations: { email: { rule: { validate: (value) => ({ valid: typeof value === 'string' && value.includes('@') }) }, message: 'A valid email is required', }, }, handle(request: EnhancedRequest): Response { // Request is validated before reaching handle() return Response.json({ success: true }) }, } ``` Validation failures return a `422` response: ```json { "error": "Validation failed", "errors": { "email": ["A valid email is required"] } } ``` ## Response Factory ```typescript import { response } from '@stacksjs/router' // JSON (200) response.json({ message: 'OK' }) response.json(data, { status: 200, headers: { 'X-Custom': 'value' }, pretty: true }) // Status shortcuts response.created({ id: 1 }) // 201 response.noContent() // 204 response.badRequest({ error: 'Bad' }) // 400 response.unauthorized() // 401 (default: { error: 'Unauthorized' }) response.forbidden() // 403 (default: { error: 'Forbidden' }) response.notFound() // 404 (default: { error: 'Not Found' }) response.error() // 500 (default: { error: 'Internal Server Error' }) // Other content types response.text('Hello', { status: 200 }) response.html('<h1>Hello</h1>') response.redirect('/new-url', 302) // 301 | 302 | 303 | 307 | 308 ``` All methods return a standard `Response` object. ## Enhanced Request (EnhancedRequest) Route handlers receive an `EnhancedRequest` (extends `Request`) with Laravel-style helper methods: ```typescript // Input access (merges query params + JSON body + form body + route params) request.get<T>(key: string, defaultValue?: T): T request.input<T>(key: string, defaultValue?: T): T request.all(): Record<string, unknown> request.only<T>(keys: string[]): T request.except<T>(keys: string[]): T // Presence checks request.has(key: string | string[]): boolean request.hasAny(keys: string[]): boolean request.filled(key: string | string[]): boolean request.missing(key: string | string[]): boolean // Typed getters request.string(key: string, defaultValue?: string): string request.integer(key: string, defaultValue?: number): number request.float(key: string, defaultValue?: number): number request.boolean(key: string, defaultValue?: boolean): boolean request.array<T>(key: string): T[] // File handling (returns UploadedFile with store/storeAs methods) request.file(key: string): UploadedFile | null request.getFiles(key: string): UploadedFile[] request.hasFile(key: string): boolean request.allFiles(): Record<string, UploadedFile | UploadedFile[]> // Authentication (set by auth middleware) await request.user(): Promise<any> await request.userToken(): Promise<any> await request.tokenCan(ability: string): Promise<boolean> await request.tokenCant(ability: string): Promise<boolean> request.bearerToken(): string | null ``` ## Request Context (AsyncLocalStorage) Access the current request from anywhere using the `request` proxy: ```typescript import { request, getCurrentRequest, runWithRequest, setCurrentRequest } from '@stacksjs/router' // The `request` export is a Proxy that reads from AsyncLocalStorage const token = request.bearerToken() const user = await request.user() // Manually run code in a request context runWithRequest(enhancedReq, () => { // request proxy works here }) ``` ## Middleware ### Defining Middleware ```typescript // app/Middleware/Auth.ts import { Middleware } from '@stacksjs/router' export default new Middleware({ name: 'Auth', priority: 1, // Lower numbers run first (default: 10) async handle(request) { const token = request.bearerToken() if (!token) { const error = new Error('Unauthorized') as Error & { statusCode: number } error.statusCode = 401 throw error } // Set authenticated user on request ;(request as any)._authenticatedUser = user }, }) ``` ### Middleware Alias Map
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기