You have built a few web pages, maybe wired up a contact form, and now you keep hearing the same three letters everywhere: API. Every mobile app, single-page React project, and microservice on the internet talks to a server somehow, and most of the time that conversation happens over a REST API. The good news? You can build a REST API with Node.js and Express in an afternoon, even if you have never written backend code before.
This guide walks you through the entire process: setting up the project, writing routes, handling data with CRUD operations, adding middleware, and avoiding the mistakes that trip up most beginners. By the time you finish, you will have a working API you actually understand, line by line.
What Is a REST API?
A REST API (Representational State Transfer Application Programming Interface) is a set of rules that lets two systems exchange data over HTTP using predictable URLs and standard methods like GET, POST, PUT, and DELETE. Each URL represents a resource, such as a user or a product, and the HTTP method describes what you want to do with it.
Think of a REST API like a restaurant. The menu (your endpoints) tells the customer what is available. The waiter (the HTTP request) carries your order to the kitchen (the server). The kitchen prepares the dish and the waiter brings back a response — usually formatted as JSON. You never see the kitchen, and you do not need to. That separation between client and server is the heart of REST.
Why Node.js and Express?
Node.js lets you run JavaScript outside the browser, which means you can use one language across your entire stack. That alone removes a huge amount of mental context-switching. But raw Node.js is fairly low-level — you would have to manually parse URLs, read request bodies, and set headers for every response.
Express sits on top of Node.js and handles that tedious work for you. It is a minimal, unopinionated framework that gives you clean routing, easy middleware, and a small API surface you can learn quickly. Here is why the combination works so well for a first project:
- Low barrier to entry: a working server fits in about ten lines of code.
- Huge ecosystem: the npm registry has a package for almost any need.
- Flexible: Express does not force a folder structure or database on you.
- Battle-tested: it powers production APIs at companies of every size.
Setting Up Your Node.js Project
Before you write any code, you need Node.js installed. Download the LTS version from the official Node.js website, then confirm it works by checking the version in your terminal.
# Verify Node.js and npm are installed
node --version
npm --version
If both commands print a version number, you are ready. The node command runs JavaScript files, while npm (Node Package Manager) installs and manages the libraries your project depends on.
Now create a project folder and initialize it. The -y flag accepts all the default settings so you can skip the interactive questions.
# Create and enter your project folder
mkdir first-rest-api
cd first-rest-api
# Initialize a package.json file
npm init -y
# Install Express as a dependency
npm install express
The npm init -y command creates a package.json file, which records your project’s metadata and dependencies. The npm install express command downloads Express into a node_modules folder and adds it to your dependency list. You only need to do this once per project.
Building Your First Express Server
Create a file named server.js in your project root. This will be the entry point for your REST API with Node.js and Express. Start with the smallest possible server that responds to a request.
// Import the Express library
const express = require('express');
// Create an Express application instance
const app = express();
// Tell Express to automatically parse incoming JSON request bodies
app.use(express.json());
// Define the port the server will listen on
const PORT = 3000;
// A simple route to confirm the server is alive
app.get('/', (req, res) => {
res.json({ message: 'Your REST API is running!' });
});
// Start the server
app.listen(PORT, () => {
console.log(`Server listening on http://localhost:${PORT}`);
});
Run it with node server.js, then open http://localhost:3000 in your browser. You should see the JSON message. The app.use(express.json()) line is important — it is middleware that reads JSON from incoming requests and makes it available on req.body. Without it, POST and PUT requests would arrive empty.
Tip: Install
nodemonwithnpm install --save-dev nodemonand run your server withnpx nodemon server.js. It restarts the server automatically every time you save a file, so you stop manually killing and rerunning the process.
Creating CRUD Endpoints
Almost every REST API performs four core actions, collectively known as CRUD: Create, Read, Update, and Delete. Each maps cleanly to an HTTP method. To keep this tutorial focused, we will store data in a simple in-memory array instead of a database, so you can concentrate on the API logic first.
| Action | HTTP Method | Example Endpoint |
|---|---|---|
| Read all | GET | /api/books |
| Read one | GET | /api/books/:id |
| Create | POST | /api/books |
| Update | PUT | /api/books/:id |
| Delete | DELETE | /api/books/:id |
Add the following data store and routes to your server.js file, above the app.listen call.
// A temporary in-memory data store (resets when the server restarts)
let books = [
{ id: 1, title: 'Clean Code', author: 'Robert C. Martin' },
{ id: 2, title: 'The Pragmatic Programmer', author: 'Andy Hunt' }
];
// READ all books
app.get('/api/books', (req, res) => {
res.json(books);
});
// READ a single book by id
app.get('/api/books/:id', (req, res) => {
const book = books.find(b => b.id === Number(req.params.id));
if (!book) {
return res.status(404).json({ error: 'Book not found' });
}
res.json(book);
});
The :id in the route is a route parameter. Express captures whatever appears in that position of the URL and exposes it on req.params.id. Because URL values arrive as strings, we convert it with Number() before comparing. Notice the 404 status code when a book is missing — returning correct status codes is what separates a polished API from a confusing one.
Handling POST and PUT Requests
Now add the routes that create and update data. These rely on the JSON middleware you set up earlier, since the client sends data in the request body.
// CREATE a new book
app.post('/api/books', (req, res) => {
const { title, author } = req.body;
// Basic validation before accepting the data
if (!title || !author) {
return res.status(400).json({ error: 'Title and author are required' });
}
const newBook = {
id: books.length ? books[books.length - 1].id + 1 : 1,
title,
author
};
books.push(newBook);
// 201 means "Created" — the correct status for a successful POST
res.status(201).json(newBook);
});
// UPDATE an existing book
app.put('/api/books/:id', (req, res) => {
const book = books.find(b => b.id === Number(req.params.id));
if (!book) {
return res.status(404).json({ error: 'Book not found' });
}
// Update only the fields that were sent
book.title = req.body.title ?? book.title;
book.author = req.body.author ?? book.author;
res.json(book);
});
The POST route validates that both fields exist before saving, returning a 400 Bad Request when they do not. The PUT route uses the nullish coalescing operator (??) so that fields the client omits keep their old values instead of becoming undefined. Returning the updated resource in the response is a common REST convention that saves the client an extra request.
Handling DELETE Requests
// DELETE a book
app.delete('/api/books/:id', (req, res) => {
const index = books.findIndex(b => b.id === Number(req.params.id));
if (index === -1) {
return res.status(404).json({ error: 'Book not found' });
}
books.splice(index, 1);
// 204 means "No Content" — success with nothing to return
res.status(204).send();
});
Here we use findIndex instead of find because splice needs the position of the item to remove it from the array. A 204 No Content response tells the client the deletion worked but there is no body to send back. Your API now supports all four CRUD operations.
Understanding Middleware in Express
Middleware is the concept that makes Express so flexible, and it is simpler than the name suggests. A middleware function runs between receiving a request and sending a response. It receives the request object, the response object, and a next function that passes control to the next function in the chain.
You already used built-in middleware with express.json(). You can also write your own. This example logs every incoming request, which is handy during development.
// Custom logging middleware
app.use((req, res, next) => {
console.log(`${req.method} ${req.url} - ${new Date().toISOString()}`);
next(); // Pass control to the next handler
});
Place this near the top of your file, after express.json() but before your routes. The critical detail is calling next() — forget it, and the request hangs forever because Express never moves on. Middleware is also where you add things like authentication checks, rate limiting, and error handling as your API grows.
Testing Your REST API
You cannot test POST, PUT, or DELETE requests by typing a URL into a browser, because browsers only send GET requests that way. You have two solid options.
- A GUI tool like Postman, which lets you choose the method, set headers, and send a JSON body through a friendly interface.
- The command line with
curl, which is fast once you know the syntax.
# Create a new book with curl
curl -X POST http://localhost:3000/api/books \
-H "Content-Type: application/json" \
-d '{"title":"You Dont Know JS","author":"Kyle Simpson"}'
# Read all books
curl http://localhost:3000/api/books
The -X flag sets the HTTP method, -H adds a header telling the server the body is JSON, and -d supplies the data. If you get your new book back with a 201 status, every layer of your API is working together correctly.
Best Practices for Production APIs
The API you built is great for learning, but a few habits will make it ready for real projects. Adopt these early and they become second nature.
- Use environment variables for the port and secrets with a package like
dotenv, instead of hard-coding values. - Version your API by prefixing routes with
/api/v1so you can introduce breaking changes later without disrupting existing clients. - Centralize error handling with a dedicated error-handling middleware (one with four arguments) rather than repeating
try/catcheverywhere. - Add security headers with Helmet and enable CORS only for the origins you trust.
- Move to a real database such as PostgreSQL or MongoDB once you outgrow the in-memory array — your data should survive a restart.
Common Pitfalls to Avoid
Most beginner bugs in a Node.js and Express API come from a small set of recurring mistakes. Watch for these:
- Forgetting
express.json(): ifreq.bodyisundefinedon a POST request, this middleware is almost always missing. - Wrong status codes: returning
200for everything hides errors from clients. Use201,400,404, and500where they belong. - Sending two responses: calling
res.json()twice throws a “headers already sent” error. Alwaysreturnafter sending a response inside a conditional. - Comparing strings to numbers:
req.params.idis a string, so'1' === 1isfalse. Convert before comparing. - Blocking the event loop: heavy synchronous work freezes every request. Keep route handlers fast and use asynchronous code for I/O.
Frequently Asked Questions
What is the difference between REST and a REST API?
REST is an architectural style — a set of principles for designing networked applications, such as statelessness and resource-based URLs. A REST API is a concrete implementation that follows those principles to let clients and servers communicate over HTTP.
Do I need a database to build a REST API with Node.js and Express?
No. You can start with an in-memory array, as we did here, which is perfect for learning the request-response flow. A database becomes necessary only when you need your data to persist after the server restarts or to handle many users at once.
What is the difference between PUT and PATCH?
PUT is intended to replace an entire resource, while PATCH updates only specific fields. In practice many APIs use PUT for partial updates too, but using PATCH for partial changes communicates your intent more clearly.
How do I secure my Express API?
Start with HTTPS, validate all incoming input, add security headers with Helmet, and protect sensitive routes with authentication such as JSON Web Tokens. Never trust data from the client, and keep secrets in environment variables rather than your code.
Is Express still a good choice in 2026?
Yes. Express remains one of the most widely used Node.js frameworks, with a massive ecosystem and excellent documentation. Newer frameworks like Fastify offer performance gains, but Express is still the best place to learn backend fundamentals.
Conclusion
You just built a complete REST API with Node.js and Express — from an empty folder to a server that creates, reads, updates, and deletes data with proper status codes and validation. More importantly, you understand why each piece exists: how routes map to resources, how middleware processes requests, and how the right HTTP methods make your API predictable.
The next steps are natural extensions of what you already know. Swap the in-memory array for a real database, split your routes into separate files as the project grows, and add authentication to protect your endpoints. Keep building small APIs, break them on purpose to see what happens, and the patterns will stick. Your first REST API is rarely your best one — but it is the one that makes every API after it easier.







