We'll create a simple task management API with the following features:
- Create and list tasks
- Mark tasks as complete
- Delete tasks
- Filter by status
This tutorial takes approximately 20 minutes to complete.
Create a new project directory and initialize it:
mkdir my-task-api
cd my-task-api
npm init -y
npm install your-package
Create a config.ts file:
export const config = {
port: process.env.PORT || 3000,
database: {
url: process.env.DATABASE_URL || "sqlite://tasks.db",
},
auth: {
secret: process.env.JWT_SECRET || "your-secret-key",
},
};
In production, always use environment variables for sensitive data. Never
commit secrets to your repository.
Create your task model:
export interface Task {
id: string;
title: string;
description?: string;
completed: boolean;
createdAt: Date;
updatedAt: Date;
}
export type CreateTaskInput = Pick<Task, "title" | "description">;
export type UpdateTaskInput = Partial<CreateTaskInput> & {
completed?: boolean;
};
Implement business logic in a service class:
import { Task, CreateTaskInput, UpdateTaskInput } from "../models/task";
export class TaskService {
private tasks: Map<string, Task> = new Map();
async create(input: CreateTaskInput): Promise<Task> {
const task: Task = {
id: crypto.randomUUID(),
title: input.title,
description: input.description,
completed: false,
createdAt: new Date(),
updatedAt: new Date(),
};
this.tasks.set(task.id, task);
return task;
}
async findAll(completed?: boolean): Promise<Task[]> {
const tasks = Array.from(this.tasks.values());
if (completed !== undefined) {
return tasks.filter((t) => t.completed === completed);
}
return tasks;
}
async findById(id: string): Promise<Task | null> {
return this.tasks.get(id) || null;
}
async update(id: string, input: UpdateTaskInput): Promise<Task | null> {
const task = this.tasks.get(id);
if (!task) return null;
const updated = {
...task,
...input,
updatedAt: new Date(),
};
this.tasks.set(id, updated);
return updated;
}
async delete(id: string): Promise<boolean> {
return this.tasks.delete(id);
}
}
For a real application, replace the in-memory Map with a proper database
like PostgreSQL or MongoDB.
Create API endpoints:
import { TaskService } from "../services/task.service";
const taskService = new TaskService();
export const taskRoutes = {
// Get all tasks
"GET /tasks": async (req, res) => {
const { completed } = req.query;
const tasks = await taskService.findAll(
completed === "true"
? true
: completed === "false"
? false
: undefined
);
res.json(tasks);
},
// Get task by ID
"GET /tasks/:id": async (req, res) => {
const task = await taskService.findById(req.params.id);
if (!task) {
return res.status(404).json({ error: "Task not found" });
}
res.json(task);
},
// Create new task
"POST /tasks": async (req, res) => {
const task = await taskService.create(req.body);
res.status(201).json(task);
},
// Update task
"PATCH /tasks/:id": async (req, res) => {
const task = await taskService.update(req.params.id, req.body);
if (!task) {
return res.status(404).json({ error: "Task not found" });
}
res.json(task);
},
// Delete task
"DELETE /tasks/:id": async (req, res) => {
const deleted = await taskService.delete(req.params.id);
if (!deleted) {
return res.status(404).json({ error: "Task not found" });
}
res.status(204).send();
},
};
Create your main application file:
import { config } from "./config";
import { taskRoutes } from "./api/tasks";
const app = createApp();
// Register routes
Object.entries(taskRoutes).forEach(([route, handler]) => {
const [method, path] = route.split(" ");
app[method.toLowerCase()](path, handler);
});
app.listen(config.port, () => {
console.log(`Server running on port ${config.port}`);
});
Try creating a task:
curl -X POST http://localhost:3000/tasks \
-H "Content-Type: application/json" \
-d '{"title":"My first task","description":"Learn the API"}'
List all tasks:
curl http://localhost:3000/tasks
Congratulations! You've built your first API.