I learn best by building. So instead of reading another article about Express, I built a small Contact Form API with two endpoints and forced myself to use req.body, req.params, req.query, and res.json() in a real context.
Here's what I built and what I actually learned.
The Setup
Two endpoints:
POST /contact โ accepts name, email, message. Saves to an array. Returns the created entry.
GET /contacts โ returns all submissions, with optional name filtering.
No database. Just an in-memory array. Simple enough to stay focused, real enough to surface actual problems.
req.body โ The Envelope
When a client sends a POST request with JSON data, that data lives in req.body. But Express doesn't read it automatically โ you have to tell it to:
app.use(express.json())
Without that line, req.body is undefined. I think of it as opening the envelope before reading the letter.
req.query โ The Optional Extras
For the GET route, I added filtering by name. Someone hits /contacts?name=Jeffrey and gets back only their submission.
js
if (!req.query.name) {
res.json(submissions)
} else {
const found = submissions.filter(sub => sub.name === req.query.name)
res.json(found)
}
The key lesson: check for undefined, not "". When no query param is sent, req.query.name is undefined โ not an empty string.
req.params โ The URL Placeholder
I added a third endpoint โ GET /contact/:id โ to fetch a single submission. The :id is a named placeholder. Whatever the client puts there, Express captures it as req.params.id
One gotcha: req.params.id is always a string. My IDs were numbers. So I had to convert:
js
const id = Number(req.params.id)
const found = submissions.find(sub => sub.id === id)
if (!found) return res.status(404).json({ error: "Not found" })
res.json(found)
Also โ use find for single items, not filter. Filter always returns an array. Find returns the object itself.
res.status().json() โ Talking Back Properly
Status codes are not optional decoration. They tell the client exactly what happened:
201 โ something was created
200 โ request succeeded
404 โ resource doesn't exist
400 โ the client sent bad data
js
res.status(201).json({
message: "Submission received",
data: newSubmission
})
Wrapping your response in a { message, data } shape is a clean pattern that scales as your API grows.
What the In-Memory Array Taught Me
Every time the server restarted, the array wiped. Frustrating at first, clarifying after โ it made the need for a database concrete, not abstract. You feel the limitation before you learn the solution. That's a better order than most tutorials give you.
If you're learning backend, build something broken first. It teaches faster than building something perfect.













