Skip to content

Quick Start

This guide will walk you through building a simple RESTful API with Shokupan.

A simple TODO API with the following features:

  • List all todos
  • Get a single todo
  • Create a new todo
  • Update a todo
  • Delete a todo

First, make sure you have Shokupan installed.

Create a new file app.ts:

import { Shokupan } from 'shokupan';
const app = new Shokupan({
port: 3000,
development: true
});
// In-memory storage (use a database in production!)
const todos: Array<{ id: number; title: string; completed: boolean }> = [];
let nextId = 1;
// Routes will go here
app.listen();
console.log('🍞 Server running at http://localhost:3000');
app.get('/todos', (ctx) => {
return { todos };
});
app.get('/todos/:id', (ctx) => {
const id = parseInt(ctx.params.id);
const todo = todos.find(t => t.id === id);
if (!todo) {
return ctx.json({ error: 'Todo not found' }, 404);
}
return { todo };
});
app.post('/todos', async (ctx) => {
const body = await ctx.body();
const todo = {
id: nextId++,
title: body.title,
completed: false
};
todos.push(todo);
return ctx.json({ todo }, 201);
});
app.put('/todos/:id', async (ctx) => {
const id = parseInt(ctx.params.id);
const todo = todos.find(t => t.id === id);
if (!todo) {
return ctx.json({ error: 'Todo not found' }, 404);
}
const body = await ctx.body();
if (body.title !== undefined) todo.title = body.title;
if (body.completed !== undefined) todo.completed = body.completed;
return { todo };
});
app.delete('/todos/:id', (ctx) => {
const id = parseInt(ctx.params.id);
const index = todos.findIndex(t => t.id === id);
if (index === -1) {
return ctx.json({ error: 'Todo not found' }, 404);
}
todos.splice(index, 1);
return { message: 'Todo deleted' };
});

Let’s add validation using Zod:

Terminal window
bun add zod

Update your todo creation route:

import { validate } from 'shokupan';
import { z } from 'zod';
const createTodoSchema = z.object({
title: z.string().min(1, 'Title is required'),
});
app.post('/todos',
validate({ body: createTodoSchema }),
async (ctx) => {
const body = await ctx.body();
const todo = {
id: nextId++,
title: body.title,
completed: false
};
todos.push(todo);
return ctx.json({ todo }, 201);
}
);

Let’s add a simple logging middleware:

app.use(async (ctx, next) => {
const start = Date.now();
console.log(`${ctx.method} ${ctx.path}`);
const result = await next();
const duration = Date.now() - start;
console.log(`${ctx.method} ${ctx.path} - ${duration}ms`);
return result;
});

For better organization, let’s refactor using controllers:

import { Controller, Get, Post, Put, Delete, Param, Body, Ctx } from 'shokupan';
import { validate } from 'shokupan';
import { z } from 'zod';
const todos: Array<{ id: number; title: string; completed: boolean }> = [];
let nextId = 1;
const createTodoSchema = z.object({
title: z.string().min(1),
});
export class TodoController {
@Get('/')
getAllTodos() {
return { todos };
}
@Get('/:id')
getTodo(@Param('id') idStr: string, @Ctx() ctx: any) {
const id = parseInt(idStr);
const todo = todos.find(t => t.id === id);
if (!todo) {
return ctx.json({ error: 'Todo not found' }, 404);
}
return { todo };
}
@Post('/')
async createTodo(@Body() body: any) {
const todo = {
id: nextId++,
title: body.title,
completed: false
};
todos.push(todo);
return { todo };
}
@Put('/:id')
async updateTodo(
@Param('id') idStr: string,
@Body() body: any,
@Ctx() ctx: any
) {
const id = parseInt(idStr);
const todo = todos.find(t => t.id === id);
if (!todo) {
return ctx.json({ error: 'Todo not found' }, 404);
}
if (body.title !== undefined) todo.title = body.title;
if (body.completed !== undefined) todo.completed = body.completed;
return { todo };
}
@Delete('/:id')
deleteTodo(@Param('id') idStr: string, @Ctx() ctx: any) {
const id = parseInt(idStr);
const index = todos.findIndex(t => t.id === id);
if (index === -1) {
return ctx.json({ error: 'Todo not found' }, 404);
}
todos.splice(index, 1);
return { message: 'Todo deleted' };
}
}
// Mount the controller
app.mount('/todos', TodoController);

Start your server:

Terminal window
bun run app.ts

Test with curl:

Terminal window
# Create a todo
curl -X POST http://localhost:3000/todos \
-H "Content-Type: application/json" \
-d '{"title": "Learn Shokupan"}'
# Get all todos
curl http://localhost:3000/todos
# Get a specific todo
curl http://localhost:3000/todos/1
# Update a todo
curl -X PUT http://localhost:3000/todos/1 \
-H "Content-Type: application/json" \
-d '{"completed": true}'
# Delete a todo
curl -X DELETE http://localhost:3000/todos/1

Great! You’ve built your first Shokupan API. Now explore: