Skip to content

Controllers

Controllers provide a structured, class-based approach to defining routes using TypeScript decorators. This pattern is familiar to NestJS developers and helps organize your application.

Create a controller using the @Get, @Post, etc. decorators:

import { Get, Post, Put, Delete, Param, Body } from 'shokupan';
export class UserController {
@Get('/')
async getAllUsers() {
return {
users: ['Alice', 'Bob', 'Charlie']
};
}
@Get('/:id')
async getUserById(@Param('id') id: string) {
return {
id,
name: 'Alice',
email: 'alice@example.com'
};
}
@Post('/')
async createUser(@Body() body: any) {
return {
message: 'User created',
data: body
};
}
@Put('/:id')
async updateUser(
@Param('id') id: string,
@Body() body: any
) {
return {
message: 'User updated',
id,
data: body
};
}
@Delete('/:id')
async deleteUser(@Param('id') id: string) {
return { message: 'User deleted', id };
}
}

Mount a controller to a base path:

import { Shokupan } from 'shokupan';
const app = new Shokupan();
// Mount at /api/users
app.mount('/api/users', UserController);
// Routes available:
// GET /api/users
// GET /api/users/:id
// POST /api/users
// PUT /api/users/:id
// DELETE /api/users/:id

All HTTP methods are supported:

import { Get, Post, Put, Patch, Delete, Options, Head, All } from 'shokupan';
export class ApiController {
@Get('/resource')
getResource() { }
@Post('/resource')
createResource() { }
@Put('/resource/:id')
replaceResource() { }
@Patch('/resource/:id')
updateResource() { }
@Delete('/resource/:id')
deleteResource() { }
@Options('/resource')
resourceOptions() { }
@Head('/resource')
resourceHead() { }
@All('/webhook')
handleWebhook() { }
}

Extract data from requests using parameter decorators:

Extract path parameters:

@Get('/users/:userId/posts/:postId')
getPost(
@Param('userId') userId: string,
@Param('postId') postId: string
) {
return { userId, postId };
}

Extract query parameters:

@Get('/search')
search(
@Query('q') searchQuery: string,
@Query('page') page?: string
) {
return {
query: searchQuery,
page: page || '1'
};
}

Access the request body:

@Post('/users')
async createUser(@Body() userData: any) {
// userData is already parsed
return { created: userData };
}

Access request headers:

@Get('/protected')
getData(
@Headers('authorization') token: string,
@Headers('user-agent') userAgent: string
) {
return { token, userAgent };
}

Access the full context object:

import { Ctx, ShokupanContext } from 'shokupan';
@Get('/info')
getInfo(@Ctx() ctx: ShokupanContext) {
return {
method: ctx.method,
path: ctx.path,
headers: Object.fromEntries(ctx.headers.entries())
};
}

Access the request object directly:

import { Req } from 'shokupan';
@Post('/upload')
async upload(@Req() req: Request) {
const formData = await req.formData();
return { uploaded: true };
}

Apply middleware to all routes in a controller using @Use:

import { Use } from 'shokupan';
const authMiddleware = async (ctx, next) => {
if (!ctx.headers.get('authorization')) {
return ctx.json({ error: 'Unauthorized' }, 401);
}
ctx.state.user = { id: '123' };
return next();
};
@Use(authMiddleware)
export class AdminController {
@Get('/dashboard')
getDashboard(@Ctx() ctx: any) {
return { user: ctx.state.user };
}
@Get('/settings')
getSettings(@Ctx() ctx: any) {
return { user: ctx.state.user };
}
}

Apply middleware to specific methods:

const logRequest = async (ctx, next) => {
console.log(`${ctx.method} ${ctx.path}`);
return next();
};
export class UserController {
@Get('/')
getAllUsers() {
return { users: [] };
}
@Post('/')
@Use(logRequest)
createUser(@Body() body: any) {
return { created: body };
}
}

Use the DI container in controllers:

import { Container } from 'shokupan';
class UserService {
getUsers() {
return ['Alice', 'Bob'];
}
}
Container.register('userService', UserService);
export class UserController {
private userService: UserService;
constructor() {
this.userService = Container.resolve('userService');
}
@Get('/')
getAllUsers() {
return { users: this.userService.getUsers() };
}
}

Organize your app with multiple controllers:

user.controller.ts
export class UserController {
@Get('/')
getUsers() { }
}
// post.controller.ts
export class PostController {
@Get('/')
getPosts() { }
}
// app.ts
import { Shokupan } from 'shokupan';
const app = new Shokupan();
app.mount('/api/users', UserController);
app.mount('/api/posts', PostController);

For controllers to work, enable decorators in tsconfig.json:

{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}