Skip to content

Routing and HTTP Methods

Routing maps incoming requests — identified by HTTP method and URL path — to handler functions. Defining clean, RESTful routes is one of the most important skills in API development.

HTTP defines several methods, each with a semantic meaning:

MethodMeaningExample
GETRetrieve data (read-only, no body)Get all posts, get one post
POSTCreate a new resourceCreate a post
PUTReplace a resource entirelyReplace a post
PATCHUpdate part of a resourceUpdate just the title
DELETERemove a resourceDelete a post

Express has a method for each: app.get(), app.post(), app.put(), app.patch(), app.delete().

REST (Representational State Transfer) is a set of conventions for naming API routes. Following them makes your API intuitive for anyone who has used an API before:

GET /posts → list all posts
GET /posts/:id → get one post
POST /posts → create a post
PATCH /posts/:id → update a post
DELETE /posts/:id → delete a post
GET /posts/:id/comments → list comments for a post
POST /posts/:id/comments → add a comment

The pattern: plural noun for the collection, /:id for a single resource, nested paths for sub-resources.

import express from 'express'
const app = express()
app.use(express.json())
// In-memory store for this example
const posts: { id: number; title: string; body: string }[] = []
let nextId = 1
app.get('/posts', (req, res) => {
res.json(posts)
})
app.get('/posts/:id', (req, res) => {
const post = posts.find(p => p.id === parseInt(req.params.id))
if (!post) return res.status(404).json({ error: 'Not found' })
res.json(post)
})
app.post('/posts', (req, res) => {
const { title, body } = req.body
const post = { id: nextId++, title, body }
posts.push(post)
res.status(201).json(post)
})
app.patch('/posts/:id', (req, res) => {
const post = posts.find(p => p.id === parseInt(req.params.id))
if (!post) return res.status(404).json({ error: 'Not found' })
if (req.body.title) post.title = req.body.title
if (req.body.body) post.body = req.body.body
res.json(post)
})
app.delete('/posts/:id', (req, res) => {
const index = posts.findIndex(p => p.id === parseInt(req.params.id))
if (index === -1) return res.status(404).json({ error: 'Not found' })
posts.splice(index, 1)
res.sendStatus(204)
})

Match any HTTP method or act as a catch-all:

// Handle all methods on a path
app.all('/legacy', (req, res) => {
res.status(410).json({ error: 'This endpoint is gone' })
})
// 404 catch-all — must be last
app.use((req, res) => {
res.status(404).json({ error: 'Route not found' })
})

Express matches routes in the order they are defined. Put specific routes before catch-alls:

// ✅ Specific route first
app.get('/posts/trending', handler)
app.get('/posts/:id', handler) // `:id` would match 'trending' if defined first
  1. Test your in-memory posts API with all five routes using curl or Thunder Client.
  2. Create two posts, update one, retrieve the updated one, then delete it.
  3. What happens when you request GET /posts/999 (a non-existent id)?
  4. Add the 404 catch-all route at the bottom and test requesting a non-existent path.
  • HTTP methods have semantic meanings: GET reads, POST creates, PATCH updates, DELETE removes.
  • RESTful conventions: plural noun for collections (/posts), /:id for single resources.
  • Routes are matched in order — put specific routes before wildcards.
  • app.use(handler) at the end catches all unmatched routes — useful for 404 responses.