Build a REST API

We'll build a small notes API from scratch: list, create, read, update and delete notes. Everything here runs the same on Node and Bun.

1. The server

npm install @server/next
// index.js
import server from '@server/next';

export default server()
  .get('/', () => 'Notes API');

Run it and open http://localhost:3000:

node index.js   # or: bun index.js

2. List and create

Keep the notes in a Map for now (swap it for a store like Redis to persist them). Return a value from a handler and it is sent as JSON; return a number and it becomes the status code.

import server, { status } from '@server/next';

const notes = new Map();

export default server()
  // GET /notes → every note
  .get('/notes', () => [...notes.values()])

  // POST /notes → create one from the JSON body
  .post('/notes', (ctx) => {
    const id = crypto.randomUUID();
    const note = { id, text: ctx.body.text };
    notes.set(id, note);
    return status(201).json(note);
  });

Try it:

curl localhost:3000/notes
curl -X POST localhost:3000/notes -H content-type:application/json -d '{"text":"Buy milk"}'

3. Read, update and delete one

Add a route parameter and read it from ctx.url.params. A missing note just returns 404.

  // GET /notes/:id
  .get('/notes/:id', (ctx) => notes.get(ctx.url.params.id) || 404)

  // PUT /notes/:id
  .put('/notes/:id', (ctx) => {
    const note = notes.get(ctx.url.params.id);
    if (!note) return 404;
    note.text = ctx.body.text;
    return note;
  })

  // DELETE /notes/:id → 204 No Content
  .delete('/notes/:id', (ctx) => {
    notes.delete(ctx.url.params.id);
    return 204;
  });

4. Test it

No ports or running server needed. .test() drives requests through the full middleware stack:

// index.test.js
import app from './index.js';

it('creates and reads a note', async () => {
  const api = app.test();

  const created = await api.post('/notes', { text: 'Hello' });
  expect(created.status).toBe(201);
  const { id } = await created.json();

  const read = await api.get(`/notes/${id}`);
  expect(await read.json()).toMatchObject({ text: 'Hello' });
});

Next steps

  • Persist notes in a store (Redis, the filesystem, and more) instead of a Map.
  • Add authentication so each user only sees their own notes.
  • Accept file attachments with uploads.