REST API Example
A complete REST API example with authentication, validation, and services.
Project Structure
src/
├── controllers/
│ └── users.controller.ts
├── middleware/
│ └── auth.middleware.ts
├── schemas/
│ └── user.schema.ts
├── services/
│ └── user.service.ts
└── server.ts
Server Setup
// src/server.ts
import { Server } from "balda";
const server = new Server({
port: 3000,
controllerPatterns: ["./src/controllers/**/*.ts"],
plugins: {
cors: { origin: "*" },
bodyParser: { json: { sizeLimit: "10mb" } },
},
});
server.setErrorHandler((req, res, next, error) => {
if (error.name === "ValidationError") {
return res.badRequest({ error: error.message });
}
res.internalServerError({ error: "Server error" });
});
server.listen(({ port }) => {
console.log(`Server running on http://localhost:${port}`);
});
Schemas
// src/schemas/user.schema.ts
import { z } from "zod";
export const CreateUserSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
password: z.string().min(8),
});
export const UpdateUserSchema = CreateUserSchema.partial();
export const UserSchema = z.object({
id: z.number(),
name: z.string(),
email: z.string(),
});
export const LoginSchema = z.object({
email: z.string().email(),
password: z.string(),
});
export type CreateUser = z.infer<typeof CreateUserSchema>;
export type User = z.infer<typeof UserSchema>;
Service
// src/services/user.service.ts
import { User, CreateUser } from "../schemas/user.schema";
class UserService {
private users: User[] = [];
private nextId = 1;
async findAll(): Promise<User[]> {
return this.users;
}
async findById(id: number): Promise<User | null> {
return this.users.find((u) => u.id === id) || null;
}
async findByEmail(email: string): Promise<User | null> {
return this.users.find((u) => u.email === email) || null;
}
async create(data: CreateUser): Promise<User> {
if (await this.findByEmail(data.email)) {
throw new Error("Email already exists");
}
const user: User = {
id: this.nextId++,
name: data.name,
email: data.email,
};
this.users.push(user);
return user;
}
async delete(id: number): Promise<boolean> {
const index = this.users.findIndex((u) => u.id === id);
if (index === -1) return false;
this.users.splice(index, 1);
return true;
}
}
export const userService = new UserService();
Middleware
// src/middleware/auth.middleware.ts
import { userService } from "../services/user.service";
export const authMiddleware = async (req, res, next) => {
const token = req.rawHeaders.get('authorization')?.replace("Bearer ", "");
if (!token) {
return res.unauthorized({ error: "Token required" });
}
try {
// In production, verify JWT token
// For demo, using user ID as token
const user = await userService.findById(Number(token));
if (!user) {
return res.unauthorized({ error: "Invalid token" });
}
req.user = user;
next();
} catch {
return res.unauthorized({ error: "Invalid token" });
}
};
Controller
// src/controllers/users.controller.ts
import {
controller,
get,
post,
del,
validate,
serialize,
middleware,
} from "balda";
import { z } from "zod";
import {
CreateUserSchema,
UserSchema,
LoginSchema,
} from "../schemas/user.schema";
import { userService } from "../services/user.service";
import { authMiddleware } from "../middleware/auth.middleware";
@controller("/users")
export class UsersController {
@get("/")
@serialize(z.array(UserSchema))
async getAll(req, res) {
const users = await userService.findAll();
res.json(users);
}
@get("/:id")
@serialize(UserSchema)
async getById(req, res) {
const user = await userService.findById(Number(req.params.id));
user ? res.json(user) : res.notFound({ error: "User not found" });
}
@post("/")
@validate.body(CreateUserSchema)
@serialize(UserSchema)
async create(req, res, body) {
try {
const user = await userService.create(body);
res.created(user);
} catch (error) {
return res.conflict({ error: error.message });
}
}
@del("/:id")
@middleware(authMiddleware)
async delete(req, res) {
const id = Number(req.params.id);
if (req.user.id !== id) {
return res.forbidden({ error: "Cannot delete other users" });
}
const deleted = await userService.delete(id);
deleted ? res.noContent() : res.notFound({ error: "User not found" });
}
@post("/login")
@validate.body(LoginSchema)
async login(req, res, body) {
const user = await userService.findByEmail(body.email);
if (!user) {
return res.unauthorized({ error: "Invalid credentials" });
}
// In production, verify password hash
res.json({
token: user.id.toString(),
user,
});
}
}
Testing
# Create user
curl -X POST http://localhost:3000/users \
-H "Content-Type: application/json" \
-d '{"name":"Alice","email":"alice@example.com","password":"password123"}'
# Login
curl -X POST http://localhost:3000/users/login \
-H "Content-Type: application/json" \
-d '{"email":"alice@example.com","password":"password123"}'
# Get all users
curl http://localhost:3000/users
# Delete user (requires auth)
curl -X DELETE http://localhost:3000/users/1 \
-H "Authorization: Bearer 1"
Key Takeaways
- Controllers organize routes by feature
- Services handle business logic and data access
- Middleware adds cross-cutting concerns like authentication
- Validation ensures request data integrity with Zod schemas
- Serialization defines response structure and generates OpenAPI docs
See Core Concepts for more details on each component.