Build and Debug a Small REST API with Node.js
Build a dependency-free REST API with Node.js, test it from the terminal, and use a repeatable debugging workflow when requests fail.

A small API is one of the best ways to understand HTTP. Instead of hiding the request
and response cycle behind a framework, we will use Node.js’s built-in node:http module,
send requests from the terminal, and inspect each result.
By the end, you will have two endpoints:
GET /healthchecks whether the server is running.GET /api/posts/:idreturns one post or a404response.
The request and response cycle
An HTTP interaction starts when a client sends a method and URL to a server. The server matches that request to a route, performs some work, and sends back a status code, headers, and an optional body.

n HTTP interaction starts when a client sends a method and URL to a server. The server matches that request to a route, performs some work, and sends back a status code, headers, and an optional body.
n HTTP interaction starts when a client sends a method and URL to a server. The server matches that request to a route, performs some work, and sends back a status code, headers, and an optional body.
Make one request, observe the complete response, change one thing, and repeat.
Build the API
Create the project
Open a terminal and create an empty project directory:
mkdir tiny-node-api cd tiny-node-api npm init -yOpen the directory in your editor. In Visual Studio Code, you can use
code ..Create the HTTP server
Create a file named
server.mjsand add the following code:import { createServer } from 'node:http'; const posts = new Map([ ['1', { id: 1, title: 'Understanding HTTP' }], ['2', { id: 2, title: 'Debugging Node.js' }], ]); function sendJson(response, status, data) { response.writeHead(status, { 'content-type': 'application/json; charset=utf-8', }); response.end(JSON.stringify(data)); } const server = createServer((request, response) => { const url = new URL(request.url, 'http://localhost'); console.log(`${request.method} ${url.pathname}`); if (request.method === 'GET' && url.pathname === '/health') { return sendJson(response, 200, { status: 'ok' }); } const match = url.pathname.match(/^\/api\/posts\/(\d+)$/); if (request.method === 'GET' && match) { const post = posts.get(match[1]); return post ? sendJson(response, 200, post) : sendJson(response, 404, { error: 'Post not found' }); } return sendJson(response, 404, { error: 'Route not found' }); }); server.listen(3000, '127.0.0.1', () => { console.log('API listening on http://127.0.0.1:3000'); });Start the server
Run the file directly with Node.js:
node server.mjsKeep this terminal open. When you are finished, press Ctrl + C to stop the server.
Send your first requests
Open a second terminal and request the health endpoint:
curl -i http://127.0.0.1:3000/healthThen request an existing post and one that does not exist:
curl -i http://127.0.0.1:3000/api/posts/1 curl -i http://127.0.0.1:3000/api/posts/99
Read the complete response
The -i option tells curl to include response headers. A successful request should
look similar to this:
HTTP/1.1 200 OK
content-type: application/json; charset=utf-8
{"id":1,"title":"Understanding HTTP"}
Read the response in this order:
- Status code — did the server report success or failure?
- Content type — is the body encoded in the format you expected?
- Body — does the returned data match the endpoint’s contract?
- Server log — did the method and path reach the application correctly?
This order prevents a common mistake: staring at application code before confirming what actually traveled over the network.
Debug a failing request
Suppose /api/post/1 returns 404. Compare the requested path with the route expression:
Requested: /api/post/1
Expected: /api/posts/1
^
The server is healthy; the client used the wrong URL. The request log makes that visible without adding a debugger or installing another tool.
Why does a request sometimes keep loading forever?
Every route must eventually call response.end(). In this example, sendJson() ends
every response. A branch that writes headers but never ends the response leaves the
connection open, so the client continues waiting.
Why does curl report connection refused?
Usually the server is not running, exited because of an error, or is listening on a different port. Check the server terminal first, then verify the host and port in the URL.
Improve the API safely
Once both routes work, extend the server one behavior at a time:
- Add
GET /api/poststo return every post. - Reject unsupported methods with
405 Method Not Allowed. - Validate data before creating or updating a resource.
- Add a request ID so one request can be followed across multiple log messages.
- Write automated tests for successful and unsuccessful responses.
The built-in server is excellent for learning how HTTP works. For a production project, you may eventually choose a framework, but the same request, routing, response, and debugging principles still apply.
Further readingNode.js HTTP documentation (external link)Explore the complete API for creating HTTP servers and handling requests and responses.A reusable debugging checklist
When an API request fails, work through these questions:
- Is the server process running?
- Am I using the correct host, port, method, and path?
- What status code and body did the server return?
- What did the server log for the same request?
- Can I reproduce the problem with the smallest possible request?
That process scales from this tiny server to much larger systems. The tools change, but careful observation remains the fastest route to the cause.
Newsletter
Learn with me
Join the newsletter to receive my latest articles. No spam, unsubscribe anytime.
You'll get a confirmation email. Check your inbox to complete your subscription.
About the author

Abdullah Islam
Abdullah Islam is a computer networking learner, tech blogger, and enthusiast who shares simple, practical guides to help others understand networking concepts and build real-world skills.
Comments
Similar posts
More articles on related topics.
A Beginner's Guide to HTTP and REST APIs
Understand the request/response cycle, status codes and the conventions that make a REST API predictable and pleasant to use.