Backend Guide
Build Your First REST API
Build a working blog API with Node.js and Express. Full CRUD routes, JSON responses, and testing — all in under 30 minutes.
📋 What You'll Need
- Node.js 18+ — see our Install Node.js guide
- A code editor
- A terminal
- 30 minutes
What you'll build: A REST API that manages blog posts in memory. Endpoints for listing, creating, reading, updating, and deleting posts. Test every route with curl.
1
Set Up the Project
mkdir first-api
cd first-api
npm init -y
npm install express
npm install -D nodemon
Update package.json to add a dev script:
{
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
}
}
2
Create the Base Server
Create index.js:
const express = require('express');
const app = express();
app.use(express.json());
const PORT = 3000;
// In-memory data store (resets on restart)
let posts = [
{ id: 1, title: 'Hello World', body: 'My first post' }
];
// GET / — sanity check
app.get('/', (req, res) => {
res.json({ message: 'API is running' });
});
// GET /posts — list all
app.get('/posts', (req, res) => {
res.json(posts);
});
// GET /posts/:id — single post
app.get('/posts/:id', (req, res) => {
const id = parseInt(req.params.id);
const post = posts.find(p => p.id === id);
if (!post) {
return res.status(404).json({ error: 'Post not found' });
}
res.json(post);
});
// POST /posts — create
app.post('/posts', (req, res) => {
const { title, body } = req.body;
if (!title || !body) {
return res.status(400).json({ error: 'Title and body are required' });
}
const newPost = {
id: posts.length ? posts[posts.length - 1].id + 1 : 1,
title,
body
};
posts.push(newPost);
res.status(201).json(newPost);
});
// PUT /posts/:id — update
app.put('/posts/:id', (req, res) => {
const id = parseInt(req.params.id);
const post = posts.find(p => p.id === id);
if (!post) {
return res.status(404).json({ error: 'Post not found' });
}
const { title, body } = req.body;
if (!title || !body) {
return res.status(400).json({ error: 'Title and body are required' });
}
post.title = title;
post.body = body;
res.json(post);
});
// DELETE /posts/:id — remove
app.delete('/posts/:id', (req, res) => {
const id = parseInt(req.params.id);
const index = posts.findIndex(p => p.id === id);
if (index === -1) {
return res.status(404).json({ error: 'Post not found' });
}
posts.splice(index, 1);
res.status(204).send();
});
app.listen(PORT, () => {
console.log(`API running at http://localhost:${PORT}`);
});
3
Start the Server
npm run dev
You should see:
API running at http://localhost:3000
4
Test with curl
Open another terminal and try each endpoint:
# List all posts
curl http://localhost:3000/posts
# Get one post
curl http://localhost:3000/posts/1
# Create a new post
curl -X POST http://localhost:3000/posts \
-H "Content-Type: application/json" \
-d '{"title":"New Post","body":"From curl"}'
# Update a post
curl -X PUT http://localhost:3000/posts/1 \
-H "Content-Type: application/json" \
-d '{"title":"Updated","body":"New content"}'
# Delete a post
curl -X DELETE http://localhost:3000/posts/2
Each returns JSON — that's your working REST API. 🎉
🧠 What You Just Built
- A working Express server on port 3000
- Five REST routes: GET all, GET one, POST, PUT, DELETE
- JSON parsing from request bodies
- Proper status codes: 200, 201, 204, 400, 404
- Input validation on POST and PUT
- In-memory data that resets on restart (real databases come next)
🚫 Common Beginner Mistakes
- Forgetting
express.json(). Without it,req.bodyis undefined. - Not validating input. Always check that required fields exist before saving.
- Returning 200 for errors. Use 400 for bad input, 404 for missing resources.
- Using
==instead of===.parseInt(req.params.id)is a number — compare with===. - Forgetting to return after sending a response. Use
return res.status(404)...to stop execution.
🎯 Practice Challenges
- Add a
PATCH /posts/:idroute that only updates provided fields - Add
GET /posts?search=helloto filter by title - Add a comments sub-resource under each post
- Persist posts to a JSON file so they survive restart
- Replace the in-memory store with PostgreSQL (Prisma or node-postgres)