From Zero to API: A Beginner's Guide to Building Your First REST API with Node.js and Express

From Zero to API: A Beginner's Guide to Building Your First REST API with Node.js and Express Developer Tools

Every API you use daily started as a single endpoint returning something embarrassingly simple. That is encouraging, because it means you do not need a heavy framework to build your first one. Node.js, Express, and an hour of focused work will get you there. This walkthrough builds a small REST API for a book collection: create, read, update, and delete. The details are small, but the patterns behind them are the same ones running inside APIs far larger than yours.

What a REST API Really Is

Strip away the jargon and REST is a set of conventions for exposing data over HTTP. You name resources with nouns, not verbs: /books rather than /getBooks. You let the HTTP method describe the action. GET reads, POST creates, PUT or PATCH updates, DELETE removes. You return JSON, and you return an honest status code: 200 for a successful read, 201 after a creation, 204 after a deletion with nothing to say back, 404 when something is not there.

The other half of REST is statelessness. Each request carries everything the server needs to answer it, with no assumption that request three knows about request two. That constraint is what lets an API scale across multiple servers later.

Setting Up the Project

Start with a version check, then a minimal folder:

  1. Confirm Node is installed: node --version. Anything reasonably recent works.
  2. Create the folder and move into it: mkdir book-api && cd book-api.
  3. Generate a package file: npm init -y.
  4. Install Express: npm install express.
  5. Create server.js, which is where everything lives for now.

That is the whole setup. Resist adding a folder structure, a config library, and a linter on day one. The project will tell you when those become necessary.

Wiring Up Your First Routes

A minimal server is three lines of intent: create an app, teach it a route, tell it to listen. Import Express, call it, add app.get('/books', ...) with a handler that sends res.json(books) back, then call app.listen(3000) and visit localhost:3000/books. Seeing your own JSON in a browser tab is a genuinely satisfying moment.

Reading input from a request

Two sources matter most. req.params pulls values out of the URL path, so a route defined as /books/:id gives you req.params.id. req.body carries the JSON payload on POST and PATCH requests, but only after you enable parsing with app.use(express.json()). Forget that line and every body arrives as undefined, which is the most common beginner stumble with Express.

Middleware in one paragraph

Middleware is any function running between the request arriving and your handler responding. express.json() is middleware. So is a logger, an authentication check, a rate limiter. Each takes (req, res, next) and either answers the request or calls next() to pass it along. Once that clicks, a lot of Express stops feeling like magic.

Storing Data for Now

Keep an array of book objects in memory and generate IDs with crypto.randomUUID(), which is built into Node and saves you a dependency. Look a book up by matching the id from req.params, and delete by finding its index and splicing it out.

Be clear-eyed about the tradeoff: in-memory data disappears when the process restarts, and it does not survive running two server instances. That is fine for learning and for prototypes. The moment you need persistence, swap the array for SQLite, PostgreSQL, or MongoDB — but do it after the routes work, not before. Reducing the number of unknowns is the fastest way to debug.

Validating Input and Handling Errors

Clients send garbage. A POST without a title should get a 400 and a short explanation, not a crash or a half-written record. Check required fields explicitly before touching your data store.

Centralize the rest. Express recognises error-handling middleware by its four arguments, (err, req, res, next), and routes anything passed to next(err) straight to it. Log the error there, then respond with a generic 500 message and no stack trace. Leaking internal paths or database details to callers is a habit worth never forming. For async handlers, remember that a rejected promise is not always caught for you; wrap handlers or use a small async helper.

Testing Before You Deploy

Start with curl, which is honest about what your API actually returns:

  • curl localhost:3000/books to confirm the read path.
  • curl -X POST localhost:3000/books -H "Content-Type: application/json" -d '{"title":"Dune"}' to check creation and the 201 status.
  • curl -i localhost:3000/books/does-not-exist to verify you get a 404 rather than a 200 with an empty body.

Then make it repeatable with Jest and Supertest. Supertest fires requests at your Express app object directly, without binding a port. The one structural change this requires is exporting app from your module and calling app.listen() in a separate entry file — a refactor worth doing anyway, since it also makes deployment configuration cleaner.

Deploying: From localhost to a Live URL

Four things to get right before your API meets the internet.

  1. Read the port from the environment: const port = process.env.PORT || 3000. Hosting platforms assign ports dynamically.
  2. Add a start script to package.json, typically "start": "node server.js", and make sure node_modules and any .env file are listed in .gitignore.
  3. Add a health endpoint such as GET /health that returns 200. It gives platforms and uptime monitors something cheap to check.
  4. Push to a Git repository, then connect it to a host. Render, Railway, and Fly.io can build and run a Node app straight from a repo; a small VPS with Nginx and PM2 is the other classic route.

Set secrets as environment variables in the host's dashboard, not in your code, and watch the first deploy's logs — connection errors surface there before anywhere else. Then hit your live URL with the same curl commands you used locally. When that JSON comes back from a real domain, you have shipped an API. The distance from here to something genuinely useful is mostly a database and some authentication. Those are good problems to have.

Photo: Daniil Komov / Pexels