Build a dependency-free JSON HTTP service with routing, body limits, correct status codes, health checks, request timeouts, and graceful shutdown.
What you will build
This tutorial produces a working, verifiable result rather than a command list. Read each command before running it, keep configuration in version control without secrets, and record the versions used for repeatable deployment.
1. Prepare the project
mkdir native-http && cd native-http
npm init -y
npm pkg set type=module scripts.start='node server.js'
Run preparation commands as an unprivileged application user unless a command explicitly requires sudo. A clean working tree and lockfile make rollback much easier.
2. Implement the solution
import { createServer } from 'node:http';
const server=createServer(async (req,res)=>{
res.setHeader('content-type','application/json; charset=utf-8');
if(req.method==='GET' && req.url==='/healthz') return res.end(JSON.stringify({status:'ok'}));
if(req.method==='POST' && req.url==='/echo') {
let body='';
for await (const chunk of req) { body+=chunk; if(body.length>1_000_000){res.writeHead(413); return res.end(JSON.stringify({error:'too_large'}));} }
try { res.end(JSON.stringify({data:JSON.parse(body)})); }
catch { res.writeHead(400); res.end(JSON.stringify({error:'invalid_json'})); }
return;
}
res.writeHead(404); res.end(JSON.stringify({error:'not_found'}));
});
server.requestTimeout=15_000; server.headersTimeout=10_000;
server.listen(3000,'127.0.0.1',()=>console.log('http://127.0.0.1:3000'));
for(const signal of ['SIGTERM','SIGINT']) process.once(signal,()=>server.close(()=>process.exit(0)));
Save the example in the filename indicated by its comment or surrounding instructions. Treat it as a minimal baseline: production applications should separate transport, business logic, persistence, and configuration into testable modules.
3. Verify end to end
npm start
curl -i http://127.0.0.1:3000/healthz
curl -i -X POST http://127.0.0.1:3000/echo -H 'content-type: application/json' -d '{"hello":"world"}'
Verification should cover both process state and a real request or data operation. A process that is merely running is not necessarily ready to serve traffic.
4. Troubleshooting and production notes
Bind to 127.0.0.1 behind a local reverse proxy, or deliberately use 0.0.0.0 inside a container. Limit request bodies before parsing them, set timeouts, return after sending a response, and log uncaught boundary errors without exposing stack traces.
Production checklist
- The supported Node.js LTS version and dependency lockfile are recorded.
- Configuration is validated at startup and secrets are stored outside source control.
- Input limits, authentication, authorization, timeouts, and error boundaries are explicit.
- Logs identify a request without exposing credentials or personal data.
- Health checks, graceful termination, resource limits, backup, and rollback have been tested.
Reference: official topic documentation. For production version selection, use a supported LTS line from the Node.js release schedule.