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
آخر نشاط في المصدر
٦ أكتوبر ٢٠٢٦ في ٢٠:٠١
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
٦٣١
التفرعات
٢٠

خيارات التثبيت

يُحدَّد 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