Handling GET and POST Requests in Express

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
Note: GET requests should never modify data on the server. They must be idempotent โ€” calling the same GET endpoint ten times must produce the same result every time without side effects. This is not just convention: browsers, CDNs, and proxies cache GET responses and may not forward them to your server at all on repeated calls.
Tip: For MERN applications, design your API endpoints around resources (nouns), not actions (verbs). Use 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.
Warning: Always validate POST request bodies before processing them. React might send malformed JSON, missing required fields, or values of the wrong type. If you pass an unvalidated body directly to 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 })

🧠 Test Yourself

A POST request arrives at /api/posts but req.body is undefined. The route is correctly defined and the server is running. What is the most likely cause?