Backend Development
Protocol context — This guide covers Stacks.js Actions and routing. Portable responsibilities appear in the draft MVA, Routing, and Request lifecycle sections.
Routing System
Stacks routing extends bun-router with Laravel-inspired patterns:
// routes/api.ts
import { router } from '@stacksjs/router'
// Basic routes
router.get('/', () => ({ message: 'Welcome to Stacks' }))
router.post('/contact', 'Actions/ContactAction')
// Route parameters
router.get('/users/{id}', 'Actions/ShowUserAction')
router.get('/posts/{post}/comments/{comment}', 'Actions/ShowCommentAction')
// Route constraints
router.get('/users/{id}', 'Actions/ShowUserAction').where('id', '[0-9]+')
// Named routes
router.get('/dashboard', 'Actions/DashboardAction').name('dashboard')
// Resource routes (RESTful)
router.resource('/articles', 'Actions/ArticleActions')
// Creates: GET /articles, GET /articles/{id}, POST /articles,
// PUT /articles/{id}, DELETE /articles/{id}
// API resource (excludes create/edit forms)
router.apiResource('/api/products', 'Actions/ProductActions')
// Route groups
router.group({ prefix: '/admin', middleware: ['auth', 'admin'] }, () => {
router.get('/dashboard', 'Actions/Admin/DashboardAction')
router.resource('/users', 'Actions/Admin/UserActions')
})
Actions Pattern
Actions encapsulate business logic in single-responsibility classes:
// app/Actions/CreatePostAction.ts
import { Action } from '@stacksjs/actions'
import { Post } from '@stacksjs/orm'
import type { CreatePostRequest } from '@stacksjs/types'
export default class CreatePostAction extends Action {
async handle(request: CreatePostRequest) {
// Validation is handled by the schema
const post = await Post.create({
title: request.title,
content: request.content,
author_id: request.user.id,
published_at: request.publish ? new Date() : null,
})
// Dispatch events
await this.dispatch('PostCreated', post)
return post
}
rules() {
return {
title: 'required|string|max:255',
content: 'required|string',
publish: 'boolean',
}
}
authorize() {
return this.user?.can('create', Post)
}
}
Actions provide:
- Single Responsibility: One action, one purpose
- Validation: Built-in request validation
- Authorization: Access control checks
- Testability: Easy to unit test in isolation
- Reusability: Call from routes, CLI, queues, or other actions
Middleware
Middleware intercepts requests and responses:
// app/Middleware/AuthMiddleware.ts
import { Middleware } from '@stacksjs/middleware'
import type { Request, Response, Next } from '@stacksjs/types'
export default class AuthMiddleware extends Middleware {
async handle(request: Request, next: Next): Promise<Response> {
const token = request.header('Authorization')?.replace('Bearer ', '')
if (!token) {
return Response.unauthorized('Authentication required')
}
const user = await this.auth.validateToken(token)
if (!user) {
return Response.unauthorized('Invalid token')
}
request.user = user
return next(request)
}
}
Built-in middleware includes:
- CORS: Cross-origin resource sharing
- RateLimit: Request throttling
- Logger: Request/response logging
- Compress: Response compression
- SecurityHeaders: HSTS, CSP, etc.
Validation Framework
Stacks validation runs on both client and server with identical rules:
// Validation schema
import { z } from '@stacksjs/validation'
const CreateUserSchema = z.object({
name: z.string().min(2).max(100),
email: z.string().email(),
password: z.string().min(8).regex(/[A-Z]/, 'Must contain uppercase'),
role: z.enum(['user', 'admin', 'moderator']),
preferences: z.object({
newsletter: z.boolean().default(true),
theme: z.enum(['light', 'dark']).default('light'),
}).optional(),
})
type CreateUserRequest = z.infer<typeof CreateUserSchema>
Validation features:
- Type Inference: Schema generates TypeScript types
- Custom Rules: Extend with custom validation logic
- Async Validation: Database uniqueness checks
- Localization: Error messages in multiple languages
- Client Sharing: Same schema validates forms