__I am learning to code. My tutor taught me how to create an HTTP server using Express, and the same evening he gave me a task — build a basic TODO app using that knowledge.
My first thought was — "Is that even possible with this little knowledge?"
The answer, as it turns out, is yes. Completely yes.
Here is everything I learned, explained the way I wish someone had explained it to me.
First, What Even Is a Backend?
Before I wrote a single line of code, I had to understand what a backend actually does.
Think about a restaurant.
You sit down. A waiter comes. You say — "Get me a burger." The waiter goes to the kitchen, gets it, and brings it back.
Now map that to the internet —
You are the browser
The waiter is Express
The kitchen is your backend logic
The burger is the data
When you type a URL in your browser and hit enter, you are placing an order. The server's job is to take that order, do something, and bring back a response.
That's it. That's the entire internet in one analogy.
req and res — What Do They Actually Mean?
When I first saw req in Express code, I thought it meant require. It doesn't. That was my first mistake.
Here's what they actually mean —
req (request) — everything the client sends TO the server. Like when you place a food order with all your details — extra cheese, no onions, table number 5. That whole thing is the request.
res (response) — what the server sends BACK to the client. The food, or maybe "sorry, that's not available."
A simple test — if someone fills a login form with their email and password and hits submit, which one carries that data to the server?
req. Because the user is sending data to the server.
HTTP Methods — The Types of Requests
Not all requests are the same. When a client talks to a server, it has to say what type of request it is making. These are called HTTP Methods.
MethodReal Life MeaningGETI want to read or fetch somethingPOSTI want to create or send something newPATCHI want to update something partiallyDELETEI want to delete something
When you type a URL in your browser and hit enter, your browser is automatically making a GET request — because you just want to see something.
And yes — when you add a new todo, that's a POST — because you are sending new data to the server.
Designing the API Before Writing Code
Here is something I did not expect — you can design your entire backend before writing a single line of code.
A TODO app needs four operations. One for each letter of CRUD — Create, Read, Update, Delete.
GET /todos → fetch all todos
POST /todos → add a new todo
PATCH /todos/:id → mark a todo done or undone
DELETE /todos/:id → remove a todo
See that :id in the last two? That's a dynamic part of the URL. It means the client tells the server which specific todo to update or delete, right in the URL itself — like /todos/3 to target the todo with id 3.
req.body vs req.params
When the server receives a request, it needs to read the data the client sent. There are two main places data can live —
req.body — data the client sends privately inside the request. Like a form submission. You cannot see it in the URL.
req.params — data that is visible in the URL itself. Like /todos/3 — that 3 is a param. You read it as req.params.id.
A simple way to remember it —
Where is the data?req.bodyHidden inside the requestreq.paramsVisible in the URL
So for each route in our TODO app —
RouteUsesGET /todosNothing — just send back all todosPOST /todosreq.body — to get the todo textPATCH /todos/:idreq.params — to know which todoDELETE /todos/:idreq.params — to know which todo
The Code — Every Line Explained
Setup
javascriptconst express = require('express')
const app = express()
const port = 3000
app.use(express.json())
let todos = []
require('express') — import the Express library
express() — create the actual app object
express.json() — this is important. When the client sends data, it arrives as raw text. This line converts it into a proper JavaScript object so req.body actually works. Without it, req.body would be undefined.
let todos = [] — our in-memory "database". Just a plain array. Data resets when the server restarts, but that's fine for a basic app.
GET — Fetch All Todos
javascriptapp.get('/todos', function(req, res) {
res.json(todos)
})
When someone hits GET /todos, send back the entire todos array. No input needed. Simple.
POST — Add a New Todo
javascriptapp.post('/todos', function(req, res) {
const text = req.body.text
const newTodo = {
id: todos.length + 1,
text: text,
done: false
}
todos.push(newTodo)
res.json(newTodo)
})
req.body.text — grab the text the user typed from the request body
id: todos.length + 1 — dynamic id. If 0 todos exist, id is 1. If 1 exists, id is 2. And so on.
done: false — every new todo starts as not completed
todos.push(newTodo) — add it to the array
res.json(newTodo) — send back the created todo
DELETE — Remove a Todo
javascriptapp.delete('/todos/:id', function(req, res) {
const id = Number(req.params.id)
const index = todos.findIndex(function(todo) {
return todo.id === id
})
todos.splice(index, 1)
res.json({ message: "Deleted Successfully" })
})
Number(req.params.id) — req.params always gives a string. But our todo ids are numbers. So "1" === 1 is false in JavaScript — the todo would never be found. Number() converts it properly.
findIndex — goes through the array and returns the position of the matching todo (like 0, 1, 2)
splice(index, 1) — removes 1 item at that position
PATCH — Mark Done or Undone
javascriptapp.patch('/todos/:id', function(req, res) {
const id = Number(req.params.id)
const todo = todos.find(function(todo) {
return todo.id === id
})
todo.done = !todo.done
res.json(todo)
})
todos.find — unlike findIndex, this returns the actual todo object, not its position
!todo.done — the ! means "opposite of". So false becomes true and true becomes false. One line to flip the status.
Starting the Server
javascriptapp.listen(port, function() {
console.log(Example app listening on port ${port})
})
This starts the server on port 3000. Your app is now running at http://localhost:3000.
find vs findIndex — What's the Difference?
This confused me at first, so let me explain it clearly.
Imagine your todos array as a row of boxes —
Position: 0 1 2
{ id:1, text:"..." } { id:2, text:"..." } { id:3, text:"..." }
find — goes through each box and returns the actual box that matches. You get the object itself.
findIndex — goes through each box and returns the position number of the matching box (0, 1, 2...).
Use find when you want to read or update the object. Use findIndex when you want to remove it with splice — because splice needs to know where to cut.
A Mistake I Made — And Why It Matters
The most frustrating bug I hit was this — my PATCH and DELETE routes were returning errors even though my code looked correct.
The fix was one small thing —
javascript// Wrong — id is a string "1"
const id = req.params.id
// Correct — id is now a number 1
const id = Number(req.params.id)
req.params always gives you a string. But todo ids in our array are numbers. So comparing "1" === 1 always returns false — the todo is never found, and the server crashes.
Always convert req.params.id with Number() when comparing with numeric ids.
Testing With Postman
Once your server is running with node server.js, you cannot test POST, DELETE, or PATCH from a browser — the browser only makes GET requests when you type a URL.
That's where Postman comes in. It lets you make any type of HTTP request and see the response.
Here's what each test looks like —
GET — method: GET, url: http://localhost:3000/todos → returns [] initially
POST — method: POST, url: http://localhost:3000/todos, body (raw JSON): { "text": "Buy groceries" } → returns the created todo
DELETE — method: DELETE, url: http://localhost:3000/todos/1 → returns { "message": "Deleted Successfully" }
PATCH — method: PATCH, url: http://localhost:3000/todos/1 → returns the todo with done flipped to true
The Pattern Behind Every Route
After writing all four routes, I noticed something. Every single route follows the exact same pattern —
- Get the input → req.body or req.params
- Do something → read, create, update, or delete from the array
- Send a response → res.json() That's it. Every backend route you will ever write follows this pattern. Just with different logic in the middle.
What I'm Learning Next
This basic app has one big limitation — every time the server restarts, all the data is gone. That's because we're storing todos in a plain JavaScript array in memory.
The next steps are —
Nodemon — automatically restarts the server when you save a file, so you don't have to do it manually every time
A real database — like MongoDB, so todos persist even after restarting
A frontend — connecting this backend to an actual HTML page so real users can interact with it
Final Thoughts
When my tutor assigned this, I genuinely didn't know if I could do it. I didn't even know what req meant a few hours earlier.
But breaking it down step by step — the restaurant analogy, understanding req and res, designing the routes before coding, writing one route at a time — made it completely approachable.
If you're a beginner reading this, the biggest thing I'd tell you is this — don't try to understand everything at once. Understand one concept, then the next. The code will start making sense faster than you think.
The whole backend is just 40 lines of code. But understanding why every line is there? That's the real learning.
Written by a beginner, for beginners. Still learning — one route at a time.
Top comments (0)