Build a Task Manager REST API
Build a full CRUD REST API with Node.js and Express using middleware.
Introduction
30 Second Summary
Every time you check the weather, scroll social media, or order food, your app is sending requests to a backend API. Building one yourself is the fastest way to understand how the web actually works.
In this project, you will build a task manager REST API using Node.js and Express. You will create, read, update, and delete tasks by sending HTTP requests from your terminal and getting back JSON responses.
What You'll Build
A task manager API you interact with entirely from the command line. You fire off requests, get instant JSON responses, and watch your task list grow, update, and shrink in real time.
By the end of this project, you'll have:
- A working REST API running locally that handles full CRUD operations. You can create tasks, list them, update their status, and delete them with simple terminal commands.
- Hands-on experience with Express middleware. Your API rejects malformed requests with helpful error messages instead of silently accepting garbage data.
- A project structure and code style that mirrors real junior backend developer take-home assignments. Proper HTTP status codes, JSON error responses, and clean route handlers.
- Secret Mission: Add query parameter filtering so you can search tasks by keyword and filter by completion status.
Are there any prerequisites?
This project assumes you understand basic programming concepts like variables, functions, and loops. If you know Python, you are ready. Node.js should be installed on your machine already, but Step 1 will verify this and help you get set up if needed.
Before We Start
Before we dive in, let's lock in what you're about to build and why it matters.
Set Up Your Node.js Project
You need a project folder with a package.json file before you can write any server code. package.json is Node.js's equivalent of Python's requirements.txt. It tracks your project's name, version, and dependencies.
In this step, you'll create a project folder, initialize it with npm, and install Express as your web framework. You'll also configure the project to use modern JavaScript import/export syntax.
In this step, get ready to:
- Create a Node.js project folder with package.json.
- Install Express as a dependency.
- Configure the project for modern JavaScript module syntax.
Confirm your Node.js version
Express 5 requires Node.js 18 or higher. Let's quickly confirm your version is ready to go.
- Open your terminal and check your Node.js version by running:
node --version
You should see v18 or higher (such as v22.x.x or v24.x.x).
Seeing a version lower than 18?
Download the latest LTS version from nodejs.org. Run the installer, then close and reopen your terminal before retrying node --version.
help me install Node.js 18 or higher on my machine
Create your project folder
Every Node.js project lives in its own folder. You'll create one on your Desktop so it's easy to find.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir task-api
cd task-api
- Initialize a new Node.js project by running:
npm init -y
You should see output confirming that package.json was created in your task-api folder.
What does npm init -y do?
The npm init command creates a package.json file that tracks your project's metadata and dependencies. The -y flag accepts all defaults so you skip the interactive prompts.
- Open the task-api folder in Cursor. Press Cmd+Shift+P (macOS) or Ctrl+Shift+P (Windows) to open the command palette.
- Type Open Folder and press Enter.
- Navigate to your Desktop, select the task-api folder, and click Open.
Don't see the task-api folder?
Make sure you ran mkdir task-api from your Desktop directory. If you're unsure where you are, run pwd in your terminal to see your current location.
help me find my task-api folder
Install Express and enable ES modules
Express is the web framework that handles HTTP requests and routing for your API. You also need to enable ES module syntax so you can use import and export instead of the older require() style.
- Open a terminal inside Cursor by pressing Ctrl+` (backtick key, same on macOS and Windows).
- Install Express by running:
npm install express
You should see output showing packages added, including express and its dependencies.
What does npm install do?
This command downloads Express and all its dependencies into a node_modules folder. It also adds Express to the dependencies section of your package.json so anyone who clones your project can run npm install to get the same packages.
Now you need to update package.json to enable modern JavaScript modules and set up the project metadata correctly.
- In the Cursor sidebar, click on package.json to open it.
- Select all the content in the file (Cmd+A on macOS, Ctrl+A on Windows) and replace it with the following:
{
"name": "task-api",
"version": "1.0.0",
"description": "A task manager REST API built with Express",
"type": "module",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"express": "^5.2.1"
}
}
What did we change?
- "type": "module" tells Node.js to use modern import/export syntax instead of the older require() pattern. This matches how JavaScript works in the browser.
- "main": "server.js" declares the entry point file you'll create in the next step.
- "start": "node server.js" creates a shortcut so you can run your server with npm start.
- Save package.json by pressing Cmd+S (macOS) or Ctrl+S (Windows).
✔️ Awesome, I've got everything!
Great. Double check you've saved your package.json file.
ⓧ I'd like to double check the full code
Your package.json should look exactly like this:
{
"name": "task-api",
"version": "1.0.0",
"description": "A task manager REST API built with Express",
"type": "module",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"express": "^5.2.1"
}
}
Before you run the final check, what do you think will happen if Node.js tries to import Express from your project folder?
- Verify that Express is installed correctly by running this in your Cursor terminal:
ls node_modules/express
You should see a list of files and folders inside the node_modules/express directory, including package.json and lib. This confirms Express is installed and ready to use.
Seeing 'No such file or directory'?
This means npm install express did not run successfully. Make sure your terminal is inside the task-api folder (run pwd to check), then try npm install express again.
help me fix my Express installation
Your project is set up and Express is ready. Next up, you'll write your first Express server and see it respond to HTTP requests in the browser.
Create Your First Express Server
Your project folder is ready with Express installed and ES modules enabled. Now it's time to write actual server code.
In this step, you'll create a single JavaScript file that starts an HTTP server and responds to browser requests with JSON data. This is your proof that Express is wired up and working.
In this step, get ready to:
- Create a server.js file with Express imported.
- Define a route that responds with JSON.
- Run the server and verify it works in your browser.
Create your Express server file
Every Express server starts the same way: import the library, create an app instance, define at least one route, and tell the app which port to listen on.
- Create a new file called server.js in Cursor by pressing Cmd+N (macOS) or Ctrl+N (Windows), then save it immediately with Cmd+S (macOS) or Ctrl+S (Windows).
- Name the file server.js and save it inside your task-api folder.
- Paste the following code into server.js:
import express from 'express';
const app = express();
const PORT = 3000;
// Root route
app.get('/', (req, res) => {
res.json({ message: 'Task API is running' });
});
app.listen(PORT, () => {
console.log(`Task API server running at http://localhost:${PORT}`);
});
What does this code do?
- import express from 'express' loads the Express library using ES module syntax (enabled by the "type": "module" you added to package.json earlier).
- express() creates your app instance. This object is what you attach routes and middleware to.
- app.get('/', ...) defines a route. When someone visits the root path /, Express runs the callback function and sends back JSON.
- app.listen(PORT, ...) starts the server on port 3000 and logs a confirmation message to the terminal.
- Save server.js with Cmd+S (macOS) or Ctrl+S (Windows).
- Confirm server.js appears in the Explorer sidebar in Cursor, inside the task-api folder.
✔️ Awesome, I've got everything!
Great. Double check you've saved the file, then move on to running it.
ⓧ I'd like to double check the full code
Your server.js should look exactly like this:
import express from 'express';
const app = express();
const PORT = 3000;
// Root route
app.get('/', (req, res) => {
res.json({ message: 'Task API is running' });
});
app.listen(PORT, () => {
console.log(`Task API server running at http://localhost:${PORT}`);
});
Don't see server.js in the sidebar?
- Make sure you saved the file (Cmd+S / Ctrl+S) and named it exactly server.js with no extra spaces or uppercase letters.
- Confirm the file is saved inside the task-api folder, not a parent directory.
Help me find where my server.js file was saved
Start your server
Your server code is ready. Time to start it up and see Express in action.
- Open your terminal in Cursor by pressing Ctrl+` (backtick key, same shortcut on macOS and Windows).
- Start your server by running this command:
node server.js
What happens when you run this?
This command tells Node.js to execute your server.js file. The server starts and takes over your terminal. You will not be able to type other commands in that terminal until you stop the server.
You should see this printed in your terminal:
Task API server running at http://localhost:3000
Seeing an error instead?
- If you see SyntaxError: Cannot use import statement, your package.json is missing "type": "module". Open it and add that line.
- If you see Cannot find module 'express', run npm install express in your terminal first to install the dependency.
- If you see EADDRINUSE, another process is already using port 3000. Close other terminals or restart your computer.
Help me fix my node server.js error
Verify the response in your browser
Your server is running. Before you check it, predict what you'll see when you visit http://localhost:3000 in your browser. What format will the response be in?
- Open your browser and navigate to http://localhost:3000.
You should see this JSON response displayed on the page:
{"message":"Task API is running"}
That JSON object is what your route handler sends back. Your browser made a GET request to / and Express responded with exactly what you told it to.
- Return to your terminal in Cursor and press Ctrl+C to stop the server.
Your terminal is now free again and ready for more commands.
Browser showing an error or blank page?
- Make sure your terminal still shows the Task API server running message. If you accidentally pressed Ctrl+C already, run node server.js again.
- Check that the URL is exactly http://localhost:3000 with no trailing slash or extra characters.
- If you see This site can't be reached, the server is not running. Switch back to your terminal and start it again.
Help me get my Express server responding in the browser
Your server works. Next up, you'll build the actual task routes that let you create, read, and manage tasks through the API.
Build Your Task Routes
Your server is alive and responding with JSON at the root path.
A single welcome message is not an API. A REST API needs endpoints for creating and reading resources. In this step, you will build GET and POST routes that store tasks in memory, giving you your first taste of JavaScript arrays, objects, and Express's request/response cycle.
In this step, get ready to:
- Enable JSON body parsing and set up in-memory storage.
- Define GET routes to read all tasks and a single task by ID.
- Define a POST route to create new tasks and discover a validation gap.
Add JSON parsing and your first GET routes
Before your server can accept incoming data from requests, it needs middleware that knows how to parse JSON request bodies. You also need somewhere to store the tasks.
- In Cursor, open server.js.
- Find the line const PORT = 3000; and add the following code below it (above the existing root route):
// Parse JSON request bodies
app.use(express.json());
// In-memory data store
let tasks = [];
let nextId = 1;
What does this code do?
- app.use(express.json()) tells Express to automatically parse any incoming request body that has a Content-Type: application/json header, making the parsed data available at req.body.
- tasks is a plain JavaScript array that acts as your database for now.
- nextId is a counter that auto-increments every time you create a task, giving each one a unique ID.
- Now add these two GET routes below the existing root route (the app.get('/', ...) block) and above app.listen:
// GET all tasks
app.get('/tasks', (req, res) => {
res.json(tasks);
});
// GET task by ID
app.get('/tasks/:id', (req, res) => {
const id = parseInt(req.params.id);
const task = tasks.find(t => t.id === id);
if (!task) {
return res.status(404).json({ error: `Task with id ${id} not found` });
}
res.json(task);
});
What do these routes do?
- GET /tasks responds with the entire tasks array as JSON. Right now that array is empty, so you will get back [].
- req.params.id grabs whatever value appears after /tasks/ in the URL. Express passes it as a string, so parseInt() converts it to a number.
- tasks.find(t => t.id === id) searches the array for a task whose id matches. It returns undefined if nothing matches.
- If no task is found, the route responds with HTTP status 404 and a clear error message instead of crashing.
- Save server.js.
- Start your server by running this command:
node server.js
- Open a second terminal (so the server keeps running) and test your new route:
curl http://localhost:3000/tasks
You should see [] printed in your terminal. That is an empty JSON array because you have not created any tasks yet.
Windows users using PowerShell
In Windows PowerShell, curl is an alias for a different command (Invoke-WebRequest). Type curl.exe instead of curl for all commands in this project. Alternatively, use Command Prompt (cmd) where curl works as expected.
- Now test the 404 handling by requesting a task that does not exist:
curl http://localhost:3000/tasks/1
You should see {"error":"Task with id 1 not found"} because no tasks have been created yet. Your 404 handling is working.
Seeing connection refused?
- Make sure your server is still running in the first terminal. You should see the Task API server running at http://localhost:3000 message.
- If you accidentally closed it, run node server.js again in that terminal.
- Run your curl commands in a separate terminal window, not the same one running the server.
Help me debug this
Define the POST route to create tasks
You can read tasks, but you cannot create them yet. The POST route accepts a JSON body, builds a task object with an auto-generated ID and timestamp, then stores it in the array.
- Stop your server by pressing Ctrl+C in the first terminal.
- In server.js, add this route below your GET /tasks/:id route and above app.listen:
// POST create new task
app.post('/tasks', (req, res) => {
const { title, description } = req.body;
const task = {
id: nextId++,
title: title,
description: description || '',
completed: false,
createdAt: new Date().toISOString()
};
tasks.push(task);
res.status(201).json(task);
});
What does this route do?
- const { title, description } = req.body uses object destructuring to pull title and description out of the incoming JSON body.
- id: nextId++ assigns the current counter value as the ID and then increments it for the next task.
- createdAt: new Date().toISOString() stamps each task with the exact time it was created.
- res.status(201).json(task) responds with HTTP status 201 (Created) and the full task object, confirming what was stored.
- Save server.js and restart your server:
node server.js
- In your second terminal, create your first task by running this command:
macOS
curl -X POST http://localhost:3000/tasks -H "Content-Type: application/json" -d '{"title":"Learn Express"}'
Windows
curl.exe -X POST http://localhost:3000/tasks -H "Content-Type: application/json" -d "{\"title\":\"Learn Express\"}"
You should see a JSON response with your task, including an auto-generated id of 1, completed: false, and a createdAt timestamp.
- Now fetch all tasks to confirm it was stored:
curl http://localhost:3000/tasks
You should see an array containing your task. The data persists in memory as long as the server is running.
Getting an empty body or missing title?
- Make sure you included app.use(express.json()) before your routes. Without it, req.body will be undefined.
- Check that your curl command includes -H "Content-Type: application/json". Express only parses the body when this header is present.
Help me debug my POST route
Test what happens with missing data
Your routes work for valid requests. But what happens when someone sends incomplete or empty data?
- Before you run this, do you think the API will accept or reject a request with no title?
- Try creating a task with an empty JSON body:
macOS
curl -X POST http://localhost:3000/tasks -H "Content-Type: application/json" -d '{}'
Windows
curl.exe -X POST http://localhost:3000/tasks -H "Content-Type: application/json" -d "{}"
The API responds with status 201 and happily creates a task with no title. That is corrupted data sitting in your store.
- Fetch all tasks to see the damage:
curl http://localhost:3000/tasks
You will see two tasks in the array. The second one has no meaningful title. A real API should reject this request instead of storing garbage.
Why does this happen?
Your POST route trusts whatever data comes in and stores it directly. There is nothing checking whether the title field exists or contains a valid string.
This is the exact problem that middleware solves. You will fix this in the next step.
✔️ Awesome, I've got everything!
Great. Double check you have saved server.js and your server is running.
ⓧ I'd like to double check the full code
Your server.js should contain these sections in order. Scroll through your file and confirm each one is present:
- The import express line, app and PORT declarations.
- app.use(express.json()) middleware.
- let tasks = [] and let nextId = 1 data store.
- The root route app.get('/', ...) responding with Task API is running.
- GET /tasks returning res.json(tasks).
- GET /tasks/:id with parseInt, find, and 404 handling.
- POST /tasks with destructuring, the task object, tasks.push, and res.status(201).
- app.listen(PORT, ...) at the bottom.
Your task API can now create and read tasks. But it has a glaring hole: it accepts any request body, even empty ones, and silently stores bad data. Next up, you will write your first Express middleware to guard against this.
Add Validation Middleware
In the last step, you built routes that create and retrieve tasks. Your API is functional.
But there is a gap. Your POST route accepts anything, including requests with no title at all. In production, bad data silently corrupting your store is a serious bug. Middleware is the Express pattern for catching problems like this before they reach your route handler.
In this step, get ready to:
- Discover the validation gap by sending invalid data to your API.
- Write a reusable middleware function to validate task input.
- Test that invalid requests are rejected with helpful error messages.
Expose the validation gap
Before writing any fix, you need to see the problem with your own eyes. What happens when someone sends a POST request with no title?
- Switch back to the terminal where your server is running. Open a second terminal window to send requests.
- Send a POST request with an empty JSON body by running this command:
curl -X POST http://localhost:3000/tasks -H "Content-Type: application/json" -d "{}"
You get back a task with an undefined title. The API happily accepted garbage data and stored it.
- Fetch all tasks to see the corrupted data by running:
curl http://localhost:3000/tasks
Your tasks array now has an entry with no meaningful title. In a real app, this corrupt data would spread through every feature that touches tasks.
Why does this happen?
Your POST route destructures title from req.body without checking whether it exists. When the body is {}, title is undefined, and JavaScript stores that without complaint.
Write the validateTask middleware function
Middleware functions in Express sit between the incoming request and your route handler. They receive req, res, and a next function. If the data is valid, they call next() to pass control forward. If not, they send an error response and stop the chain.
- Stop your server by pressing Ctrl+C in the terminal where it is running.
- In server.js, find the comment // Root route. Add the following validation function above that line:
// Validation middleware
function validateTask(req, res, next) {
const { title } = req.body;
if (!title || typeof title !== 'string' || title.trim().length === 0) {
return res.status(400).json({ error: 'Title is required and must be a non-empty string' });
}
next();
}
What does this code do?
- const { title } = req.body pulls the title from the request body using object destructuring.
- The if condition checks three failure cases: title is missing, title is not a string, or title is only whitespace.
- If any check fails, the function sends a 400 status with a clear error message and returns early. The route handler never runs.
- next() passes control to the next function in the chain. Without this call, valid requests would hang forever.
- Now wire the middleware into your POST route. Find this line in server.js:
app.post('/tasks', (req, res) => {
- Add validateTask as the second argument, so the line now looks like this:
app.post('/tasks', validateTask, (req, res) => {
- While you are in the POST route, also find this line:
title: title,
- Replace it with title.trim() to strip whitespace from valid titles:
title: title.trim(),
- Save server.js.
- Restart your server by running:
node server.js
You should see Task API server running at http://localhost:3000 in your terminal.
Seeing a syntax error on startup?
- Check that validateTask is defined above the POST route, not inside it. Functions must be declared before they are referenced.
- Make sure you added validateTask as a separate argument with a comma: app.post('/tasks', validateTask, (req, res) => {.
- help me debug a syntax error in my Express server
✔️ Awesome, I've got everything!
Great. Double check you have saved server.js and your server restarted without errors.
ⓧ I'd like to double check the full code
Scroll through your server.js and confirm these sections are present, in order:
- The import express line, app and PORT declarations, app.use(express.json()), and the tasks array with nextId counter.
- The new validateTask function (checking title exists, is a string, and is non-empty after trimming).
- The root route (GET /), GET /tasks, and GET /tasks/:id routes (unchanged from Step 3).
- The POST /tasks route now with validateTask as middleware and title: title.trim() inside the task object.
- The app.listen call at the bottom.
Verify that validation catches bad requests
Time to test the fix. Before you run the next command, predict what you expect to happen. Will the empty POST succeed like before, or will something different happen?
- Send the same empty POST request that created garbage data earlier:
curl -X POST http://localhost:3000/tasks -H "Content-Type: application/json" -d "{}"
This time you get a 400 response with a clear error message:
{"error":"Title is required and must be a non-empty string"}
Your middleware blocked the request before it reached the route handler. No corrupt data in your store.
- Now confirm that valid requests still work. Create a task with a proper title:
curl -X POST http://localhost:3000/tasks -H "Content-Type: application/json" -d "{\"title\":\"Learn middleware\"}"
You should see a 201 response with your new task, including an auto-generated ID and timestamp.
- Fetch all tasks to confirm the clean state:
curl http://localhost:3000/tasks
Your array contains only the valid task you just created. The corrupted entries from earlier are gone too (the server restarted, clearing in-memory data).
Why is the old corrupt data gone?
Your tasks array lives in memory. Every time you restart the server with node server.js, the array resets to empty. This is expected for in-memory storage.
Still getting a 201 response with empty data?
- Make sure you added validateTask between the path string and the callback: app.post('/tasks', validateTask, (req, res) => {.
- Confirm you saved server.js and restarted the server after making changes. Changes to the file are not picked up until you restart.
- On Windows PowerShell, curl is an alias for a different command. Use curl.exe instead, or run your commands in Command Prompt.
- help me debug why my validateTask middleware is not blocking empty requests
Your API now rejects bad data at the door. Next up, you will complete the full CRUD set by adding update and delete routes.
Complete the API with Update and Delete
Your API now validates incoming data before saving it. Tasks with missing or empty titles get rejected with a clear error message.
A complete API needs all four CRUD operations. In this step, you'll add PUT and DELETE routes that introduce new JavaScript patterns: the spread operator for merging objects and findIndex for locating items in an array.
In this step, get ready to:
- Define a PUT route to update existing tasks.
- Define a DELETE route to remove tasks permanently.
- Test the full create-update-delete lifecycle with curl.
Define the PUT route for updating tasks
A PUT request updates an existing resource. You need to find the task by its ID, apply the incoming changes, and return the updated version.
- Stop your running server by pressing Ctrl+C in the terminal.
- In server.js, find the closing }); of your POST route (just above app.listen). Add the following PUT route below it:
// PUT update task
app.put('/tasks/:id', validateTask, (req, res) => {
const id = parseInt(req.params.id);
const taskIndex = tasks.findIndex(t => t.id === id);
if (taskIndex === -1) {
return res.status(404).json({ error: `Task with id ${id} not found` });
}
const { title, description, completed } = req.body;
tasks[taskIndex] = {
...tasks[taskIndex],
title: title.trim(),
description: description !== undefined ? description : tasks[taskIndex].description,
completed: completed !== undefined ? completed : tasks[taskIndex].completed
};
res.json(tasks[taskIndex]);
});
What does this code do?
This route combines several JavaScript patterns to update a task in place:
- validateTask runs first as middleware, rejecting requests without a valid title before the handler runs.
- findIndex returns the position of the task in the array, or -1 if no match exists. You need the index (not just the object) so you can replace it.
- The spread operator (...tasks[taskIndex]) copies all existing fields into a new object. Then title, description, and completed overwrite only the fields the request provides.
- The !== undefined checks let the client update just one field without wiping out the others.
- Save server.js.
- Restart your server by running:
node server.js
- Create a task so you have something to update by running:
curl -X POST http://localhost:3000/tasks -H "Content-Type: application/json" -d "{\"title\":\"Learn Express\"}"
- Now update that task by running:
curl -X PUT http://localhost:3000/tasks/1 -H "Content-Type: application/json" -d "{\"title\":\"Updated task\",\"completed\":true}"
You should see a JSON response with the updated title and completed set to true.
Seeing a 400 error instead of the update?
- Make sure your -d flag includes a title field. The validateTask middleware requires it on PUT requests too.
- On Windows PowerShell, curl is an alias for a different command. Use curl.exe instead, or run your commands in Command Prompt.
- Check that the task ID in your URL matches the ID returned when you created the task.
Still stuck? Why is my PUT route returning an error?
Define the DELETE route for removing tasks
DELETE requests remove a resource permanently. You'll use findIndex again to locate the task, then splice to remove it from the array.
- Stop your server with Ctrl+C.
- In server.js, add the following DELETE route below your PUT route (just above app.listen):
// DELETE task
app.delete('/tasks/:id', (req, res) => {
const id = parseInt(req.params.id);
const taskIndex = tasks.findIndex(t => t.id === id);
if (taskIndex === -1) {
return res.status(404).json({ error: `Task with id ${id} not found` });
}
const deleted = tasks.splice(taskIndex, 1)[0];
res.json({ message: `Task "${deleted.title}" deleted`, task: deleted });
});
What does this code do?
- splice(taskIndex, 1) removes exactly one element at the given position and returns an array of removed items. The [0] grabs the single removed task from that array.
- The response includes both a confirmation message and the deleted task object, so the client knows exactly what was removed.
- No validateTask middleware here. You don't need a valid title to delete something.
- Save server.js.
- Restart your server by running:
node server.js
- Create a task and then delete it by running these commands one at a time:
curl -X POST http://localhost:3000/tasks -H "Content-Type: application/json" -d "{\"title\":\"Temporary task\"}"
curl -X DELETE http://localhost:3000/tasks/1
You should see a JSON response confirming the task was deleted, including the task object that was removed.
Getting a 404 when deleting?
- The server restarted and cleared its memory. You need to create a new task with POST before you can delete one.
- Check the ID in your DELETE URL. The first task created after a restart always has ID 1.
Need help? Why is my DELETE route returning 404?
Test the full create-update-delete lifecycle
Time to prove the whole API works end to end. You'll run through the complete lifecycle in one session: create, update, delete, then verify it's gone.
Before you run these commands, think about what you expect the final GET request to return. Will it succeed or fail?
- With your server still running, create a fresh task:
curl -X POST http://localhost:3000/tasks -H "Content-Type: application/json" -d "{\"title\":\"Learn Express\"}"
- Update the task to mark it completed:
curl -X PUT http://localhost:3000/tasks/2 -H "Content-Type: application/json" -d "{\"title\":\"Learn Express\",\"completed\":true}"
Why ID 2?
The task you created earlier in this step was ID 1. Your server is still running with the same nextId counter, so the next task gets ID 2. If you restarted your server between the previous substep and now, use ID 1 instead.
- Delete the task:
curl -X DELETE http://localhost:3000/tasks/2
- Now try to retrieve the deleted task:
curl http://localhost:3000/tasks/2
You should see a 404 response: {"error":"Task with id 2 not found"}. The task is gone. Your API handles the full lifecycle correctly.
What are HTTP status codes 201, 400, and 404?
- 201 Created means a new resource was successfully created (your POST response).
- 400 Bad Request means the client sent invalid data (your validation middleware response).
- 404 Not Found means the requested resource does not exist (trying to GET, PUT, or DELETE a missing task).
Using the right status codes tells clients exactly what happened without them needing to parse your error messages.
✔️ Awesome, I've got everything!
Great. Double check you've saved server.js and that your server responds correctly to all four operations.
ⓧ I'd like to double check the full code
Your server.js file should contain these sections in order. Scroll through your file and confirm each one is present:
- The import express statement and app/PORT setup.
- express.json() middleware.
- The tasks array and nextId counter.
- The validateTask middleware function.
- GET / root route.
- GET /tasks (all tasks).
- GET /tasks/:id (single task).
- POST /tasks with validateTask middleware.
- PUT /tasks/:id with validateTask middleware.
- DELETE /tasks/:id route.
- app.listen at the bottom.
Your API now handles all four CRUD operations. Next up, you'll take on a secret mission to add filtering and search to your GET route.
Secret mission
Add Query Parameter Filtering
Your API returns every task every time you hit GET /tasks. Real APIs let users narrow down results with query parameters like ?completed=true or ?search=express. Add filtering to your task list endpoint so users can find exactly what they need.
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs entirely locally, so there are no ongoing costs.
Resources you used:
- The task-api project folder on your Desktop (contains server.js, package.json, and node_modules/).
- The Node.js server process running on port 3000.
Keep everything running
No action needed. Choose this if you're still actively building or experimenting with your API.
- Your server may still be running in your terminal. You can restart it any time with node server.js from inside the task-api folder.
- Your project files stay on your Desktop and take up minimal disk space.
Pause - I'll come back to this later
Stop the running server to free up your terminal. Your files stay on disk and you can restart any time.
- If your server is still running, press Ctrl+C in the terminal to stop it.
- To resume later, open a terminal, navigate to your task-api folder, and run node server.js.
Delete - I don't want to use this again
Remove all project files and start fresh. This permanently deletes your code.
- If your server is still running, press Ctrl+C in the terminal to stop it.
- Delete the project folder by running the command for your operating system.
macOS
rm -rf ~/Desktop/task-api
Windows
rmdir /s /q %USERPROFILE%\Desktop\task-api
- Confirm the folder is gone by checking your Desktop. The task-api folder should no longer appear.
Nice Work!
Nice Work!
You just built a complete REST API from scratch using Node.js and Express. You can create, read, update, and delete tasks through HTTP requests, validate input with middleware, and handle errors with proper status codes.
You've learned how to:
- Build a full CRUD API with Express handling GET, POST, PUT, and DELETE operations with proper HTTP status codes (200, 201, 400, 404) and JSON error responses.
- Use JavaScript fundamentals in context: ES modules, arrow functions, array methods (find, findIndex, filter, splice), object destructuring, spread syntax, and template literals.
- Implement Express middleware for input validation, the same pattern used in every production Node.js API.
- Secret Mission: Added query parameter filtering with combined search and completion status filters.
Ready to quiz yourself?