Building a REST API · File-based Database
Design and implement a REST API: pick correct status codes, model resources around URLs, support CRUD, and enable CORS for browser clients. Before introducing a real database, persist data to disk as JSON. Learn the limitations (concurrency, scale) and when to graduate to SQLite/PostgreSQL.
What you'll learn
- 1Design resource-based URLs
- 2Return the right status code for each scenario
- 3Implement create / read / update / delete
- 4Enable CORS for cross-origin browser requests
- 5Read/write a JSON file with fs.promises
- 6Wrap I/O in helper functions
- 7Recognize race-condition limitations
- 8Migrate naturally to a real database later
Building a REST API
Design and implement a REST API: pick correct status codes, model resources around URLs, support CRUD, and enable CORS for browser clients.
1. REST Conventions
| Method | URL | Action | Success Status |
|---|---|---|---|
| GET | /tasks | List all tasks | 200 |
| GET | /tasks/:id | Get one task | 200 (or 404) |
| POST | /tasks | Create a task | 201 |
| PUT | /tasks/:id | Replace a task | 200 |
| PATCH | /tasks/:id | Partial update | 200 |
| DELETE | /tasks/:id | Delete a task | 204 (no body) |
2. Sample CRUD
import express from "express";
import cors from "cors";
const app = express();
app.use(cors());
app.use(express.json());
let tasks = [];
let nextId = 1;
app.get("/tasks", (req, res) => res.json(tasks));
app.get("/tasks/:id", (req, res) => {
const t = tasks.find(t => t.id == req.params.id);
if (!t) return res.status(404).json({ error: "Not found" });
res.json(t);
});
app.post("/tasks", (req, res) => {
const task = { id: nextId++, title: req.body.title, done: false };
tasks.push(task);
res.status(201).json(task);
});
app.delete("/tasks/:id", (req, res) => {
tasks = tasks.filter(t => t.id != req.params.id);
res.status(204).end();
});
app.listen(3000);3. Status Codes Cheat Sheet
- 2xx — success (200 OK, 201 Created, 204 No Content)
- 3xx — redirection (301 Moved, 304 Not Modified)
- 4xx — client error (400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 409 Conflict)
- 5xx — server error (500 Internal Server Error, 503 Service Unavailable)
4. CORS
Browsers block cross-origin requests by default. Enable CORS so your frontend can call your API.
import cors from "cors";
// Allow specific origins
app.use(cors({
origin: ["http://localhost:5173", "https://myapp.com"],
credentials: true,
}));File-based Database (JSON)
Before introducing a real database, persist data to disk as JSON. Learn the limitations (concurrency, scale) and when to graduate to SQLite/PostgreSQL.
1. A Tiny JSON Store
import fs from "node:fs/promises";
const FILE = "./data.json";
async function load() {
try {
return JSON.parse(await fs.readFile(FILE, "utf8"));
} catch {
return { tasks: [], nextId: 1 };
}
}
async function save(data) {
await fs.writeFile(FILE, JSON.stringify(data, null, 2));
}
const db = await load();
db.tasks.push({ id: db.nextId++, title: "buy milk" });
await save(db);2. Integrate with Express
app.get("/tasks", async (req, res) => {
const db = await load();
res.json(db.tasks);
});
app.post("/tasks", async (req, res) => {
const db = await load();
const task = { id: db.nextId++, title: req.body.title, done: false };
db.tasks.push(task);
await save(db);
res.status(201).json(task);
});3. Limitations
- Two requests writing at the same time can lose data (race condition)
- Whole file is read/rewritten on every operation — slow past a few MB
- No query language, no indexes
- Fine for: personal tools, prototypes, low-traffic dashboards
- Not fine for: any production app with concurrent users
Next module — Databases — graduates from JSON to SQLite (single file but with SQL) and beyond.
All lecture materials and example code are openly available on GitHub.
View on GitHub ↗Comments 0
No comments yet. Be the first.
Only members can comment.
Comments with ads, abuse, personal information or illegal content are hidden or removed. Comments are shown with your nickname. Privacy Policy