Top 10 VS Code Extensions for Python Developers in 2025
October 12, 2026 - Tools
Express has been the default HTTP layer for Node.js for over a decade, and it still earns the spot: small, unopinionated, and easy to reason about six months later. Over the next stretch of work you'll build a functioning REST API for a bookmarks manager — routes, validation, middleware, error handling, and tests that run with a single command. Everything here uses stable, current versions of Node.js and Express, and nothing you'll have to rip out later.
An API is a set of promises about a resource. Decide what that resource is, which fields it carries, and which operations you support, and the code almost writes itself. Ours is a bookmark: an id, a title, a URL, optional tags, and a createdAt timestamp.
The endpoints and the status codes each should return:
Write that list down before you open an editor. It's the contract your client code and your tests will both lean on.
Start in an empty folder with Node.js 18 or newer installed, then run:
npm init -y
npm install express
npm install --save-dev nodemon
Add two scripts to package.json: "start": "node src/server.js" and "dev": "nodemon src/server.js". Nodemon restarts the process whenever a file changes, which saves a lot of manual Ctrl+C.
Keep the layout small and obvious:
Splitting app.js from server.js matters more than it looks. Tests import the app directly without binding a port, so you never fight "address already in use" errors or a stray process holding port 3000.
Create a router with express.Router() and mount it at /api/bookmarks from app.js. Each handler receives (req, res); keep them thin and push real logic into the store.
router.get('/', (req, res) => res.json(store.all()));
For a single item, read req.params.id and return 404 when the store comes back empty. A response like res.status(404).json({ error: 'Not found' }) tells the client far more than a silent 200 with a null body.
POST needs one guard before anything else: check the body you were handed.
if (!req.body.title || !req.body.url) return res.status(400).json({ error: 'title and url are required' });
Generate the id server-side with crypto.randomUUID() and set createdAt yourself. Never let the client pick the id — that's how two records end up fighting over the same key. Return 201 and the created object so the client can cache it immediately instead of re-fetching the whole collection.
PUT replaces a record; PATCH updates individual fields. Pick one, document it, and stay consistent. If you're updating fields, spread the existing record first and then apply the incoming values, so a client can't accidentally erase fields it never mentioned.
Middleware sits between the request arriving and your handler replying. Order is the whole game.
Inside the error handler, log the stack, then send res.status(err.status || 500).json({ error: 'Something went wrong' }). Keep internal messages out of the response body.
A module-level array is fine for a tutorial and honestly fine for a prototype. It resets on restart, and that's the only real cost. Wrap it in plain functions — all(), find(id), create(data), update(id, data), remove(id) — so swapping in SQLite or Postgres later means rewriting one file instead of every route.
Two habits make that swap painless. Return promises from the start, so callers already await and the function bodies just get longer. And keep HTTP concerns out of the store: no req, no res, plain data in and plain data out.
Manual checks first. With the dev server running, curl confirms the basics in seconds.
curl http://localhost:3000/api/bookmarks
curl -X POST http://localhost:3000/api/bookmarks -H "Content-Type: application/json" -d '{"title":"MDN","url":"https://developer.mozilla.org"}'
Then automate it. Supertest drives the app in-process without a live server, and Node's built-in test runner means no extra framework to configure.
const request = require('supertest');
const app = require('../src/app');
Focus on the four things that break in production: happy paths return the right status and shape, unknown ids return 404, invalid bodies return 400, and a fresh store starts empty. Reset the store in a beforeEach hook so tests never depend on the order they run in.
Read the port from process.env.PORT, add a .gitignore for node_modules, and the API is ready for a container or a small host. The highest-value additions from here are schema validation, pagination on the list endpoint, and rate limiting on writes. None of them require changing the structure you just built.
The pattern worth keeping: decide the resource, separate app from server, keep handlers thin, put data behind functions, and test failure cases as carefully as happy ones. Do that and you have an API you can extend for years without a rewrite.
Photo: Lukas Blazek / Pexels
Copyright © 2026 Code Harbor. All Right Reserved.