GET and POST requests are the two HTTP methods you will use most frequently in a MERN REST API. GET requests read data โ they fetch posts, users, or any resource from MongoDB and return it as JSON. POST requests write data โ they receive a JSON body from React, validate it, save it to MongoDB, and return the newly created resource. Understanding how to define both route types, how to read everything from the incoming request, and how to send correct responses is the core skill of Express API development.
HTTP Methods โ A REST Refresher
| Method | REST Convention | Has Body? | Example |
|---|---|---|---|
| GET | Read โ retrieve one or many resources | No | GET /api/posts, GET /api/posts/:id |
| POST | Create โ add a new resource | Yes | POST /api/posts |
| PUT | Update โ replace entire resource | Yes | PUT /api/posts/:id |
| PATCH | Update โ modify part of a resource | Yes | PATCH /api/posts/:id |
| DELETE | Remove a resource | Rarely | DELETE /api/posts/:id |
GET /api/posts not GET /api/getPosts. Use POST /api/posts not POST /api/createPost. The HTTP method itself is the verb โ your URL path should name the resource being acted upon.Post.create(req.body), a malicious user can inject arbitrary fields into your MongoDB documents (a technique called mass assignment). Always destructure only the fields you expect from req.body.Defining GET Routes
// In-memory store for this lesson โ replaced with MongoDB in Chapter 9+
let posts = [
{ id: 1, title: 'Getting Started with MERN', body: 'MERN is a powerful stack...', published: true },
{ id: 2, title: 'Node.js Basics', body: 'Node.js runs on V8...', published: true },
{ id: 3, title: 'Draft Post', body: 'Work in progress...', published: false },
];
// โโ GET all posts โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
app.get('/api/posts', (req, res) => {
// Query string filtering: GET /api/posts?published=true
const { published } = req.query;
let result = posts;
if (published !== undefined) {
result = posts.filter(p => p.published === (published === 'true'));
}
res.json({ success: true, count: result.length, data: result });
});
// โโ GET single post by ID โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
app.get('/api/posts/:id', (req, res) => {
const id = parseInt(req.params.id); // params are always strings โ parse if needed
const post = posts.find(p => p.id === id);
if (!post) {
return res.status(404).json({ success: false, message: `Post with id ${id} not found` });
}
res.json({ success: true, data: post });
});
// โโ GET all published posts by a specific tag โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
app.get('/api/posts/tag/:tag', (req, res) => {
const { tag } = req.params;
const filtered = posts.filter(p => p.tags && p.tags.includes(tag));
res.json({ success: true, count: filtered.length, data: filtered });
});
Defining POST Routes
// โโ POST โ create a new post โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
app.post('/api/posts', (req, res) => {
// Destructure only the fields we expect โ do NOT spread all of req.body
const { title, body, tags } = req.body;
// Basic validation
if (!title || !body) {
return res.status(400).json({
success: false,
message: 'Title and body are required',
});
}
if (typeof title !== 'string' || title.trim().length < 3) {
return res.status(400).json({
success: false,
message: 'Title must be a string with at least 3 characters',
});
}
// Create the post
const newPost = {
id: posts.length + 1,
title: title.trim(),
body: body.trim(),
tags: Array.isArray(tags) ? tags : [],
published: false,
createdAt: new Date().toISOString(),
};
posts.push(newPost);
// 201 Created โ not 200 โ for successful resource creation
res.status(201).json({ success: true, data: newPost });
});
Reading From the Request Object
app.get('/api/posts/:id/comments', (req, res) => {
// โโ URL parameters (:id, :slug, etc.) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const postId = req.params.id; // '/api/posts/42/comments' โ '42'
// โโ Query string (?key=value) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const sort = req.query.sort || 'createdAt';
// GET /api/posts/42/comments?page=2&limit=5&sort=likes
// โโ Request headers โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const authHeader = req.headers['authorization']; // 'Bearer eyJ...'
const contentType = req.headers['content-type']; // 'application/json'
const userAgent = req.headers['user-agent'];
res.json({ postId, page, limit, sort });
});
app.post('/api/posts', (req, res) => {
// โโ Request body (requires express.json() middleware) โโโโโโโโโโโโโโโโโโโโโ
const { title, body, tags, published } = req.body;
// Only available if:
// 1. express.json() middleware is active
// 2. Client sent Content-Type: application/json header
// 3. Request has a body (GET requests do not)
res.status(201).json({ received: { title, body } });
});
Testing GET and POST with Postman
โโ Test GET all posts โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Method: GET
URL: http://localhost:5000/api/posts
โ Expected: 200 { success: true, count: 3, data: [...] }
โโ Test GET with query filter โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Method: GET
URL: http://localhost:5000/api/posts?published=true
โ Expected: 200 { success: true, count: 2, data: [...] }
โโ Test GET single post โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Method: GET
URL: http://localhost:5000/api/posts/1
โ Expected: 200 { success: true, data: { id: 1, title: '...' } }
โโ Test GET missing post โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Method: GET
URL: http://localhost:5000/api/posts/999
โ Expected: 404 { success: false, message: 'Post with id 999 not found' }
โโ Test POST create post โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Method: POST
URL: http://localhost:5000/api/posts
Headers: Content-Type: application/json
Body (raw JSON):
{ "title": "My New Post", "body": "Post content here", "tags": ["mern"] }
โ Expected: 201 { success: true, data: { id: 4, title: 'My New Post', ... } }
โโ Test POST missing fields โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Method: POST
URL: http://localhost:5000/api/posts
Body: { "title": "No body field" }
โ Expected: 400 { success: false, message: 'Title and body are required' }
Common Mistakes
Mistake 1 โ Using req.body on a GET request
โ Wrong โ trying to send data in the body of a GET request:
// React code
axios.get('/api/posts', { body: JSON.stringify({ filter: 'published' }) });
// req.body will be undefined โ GET requests do not have a body
โ Correct โ use query strings for GET request parameters:
axios.get('/api/posts', { params: { published: true } });
// Server: req.query.published === 'true' โ
Mistake 2 โ Spreading req.body directly into a document
โ Wrong โ allowing any field from the request to be saved:
const newPost = { id: posts.length + 1, ...req.body };
// A malicious user sends: { "title": "x", "isAdmin": true, "__proto__": {} }
// All of those fields end up in your data store
โ Correct โ destructure only expected fields:
const { title, body, tags } = req.body;
const newPost = { id: posts.length + 1, title, body, tags }; // only known fields โ
Mistake 3 โ Not parsing URL params to the correct type
โ Wrong โ comparing a string param directly to a number ID:
const post = posts.find(p => p.id === req.params.id);
// req.params.id is '1' (string) โ posts have numeric ids
// '1' === 1 is false โ post is always undefined
โ Correct โ convert params to the expected type before using:
const id = parseInt(req.params.id, 10);
const post = posts.find(p => p.id === id); // number === number โ
Quick Reference
| Task | Code |
|---|---|
| GET all resources | app.get('/api/posts', handler) |
| GET one resource | app.get('/api/posts/:id', handler) |
| POST create resource | app.post('/api/posts', handler) |
| Read URL param | req.params.id |
| Read query string | req.query.page |
| Read request body | req.body.title |
| Destructure body safely | const { title, body } = req.body |
| Respond 400 | res.status(400).json({ message: '...' }) |
| Respond 201 | res.status(201).json({ data: newItem }) |