Production: security, testing and deployment · Lesson 18 of 18
Project: tasks REST API and deployment
Combine server, routing, validation and tests, then prepare it for Render or Vercel.
The capstone is a small in-memory API with GET /tasks and POST /tasks. We separate the handler from listen so tests use a temporary port, validate titles, return correct JSON and statuses, then close the server. Production would replace the array with a database.
Client
Router + validation
Task service
Database later
Tests + deploy
const http = require("node:http")
const assert = require("node:assert/strict")
const tasks = []
const server = http.createServer((req, res) => {
res.setHeader("content-type", "application/json")
if (req.method === "GET" && req.url === "/tasks") return res.end(JSON.stringify(tasks))
if (req.method === "POST" && req.url === "/tasks") {
let raw = ""
req.on("data", (chunk) => { raw += chunk })
req.on("end", () => {
const body = JSON.parse(raw)
if (typeof body.title !== "string" || !body.title.trim()) {
res.statusCode = 422
return res.end(JSON.stringify({ error: "title required" }))
}
const task = { id: tasks.length + 1, title: body.title.trim(), done: false }
tasks.push(task)
res.statusCode = 201
res.end(JSON.stringify(task))
})
return
}
res.statusCode = 404
res.end(JSON.stringify({ error: "not found" }))
})
server.listen(0, async () => {
const base = "http://127.0.0.1:" + server.address().port
const created = await fetch(base + "/tasks", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ title: "Learn Node" }) })
assert.equal(created.status, 201)
const listed = await fetch(base + "/tasks")
assert.deepEqual(await listed.json(), [{ id: 1, title: "Learn Node", done: false }])
console.log("all tests passed")
server.close()
})Render runs a long-lived Node service: bind to 0.0.0.0 and process.env.PORT, and configure a health check. Vercel serverless suits short stateless handlers; never call listen inside the function. Both need dashboard-managed environment variables, logs, timeouts and storage beyond process memory.
# Typical long-running service npm ci npm test PORT=3000 node server.js # On SIGTERM: stop accepting traffic, finish requests, close DB, then exit # Platform settings hold secrets; never commit .env
Quick question
Why are in-memory tasks unsuitable for production?
Exercise
Complete a store: create(title) adds a task with an increasing id and list() returns a copy. Test it and print all tests passed.
Expected output: all tests passed
const assert = require("node:assert/strict")
function makeStore() {
const tasks = []
return {
create(title) {
},
list() {
}
}
}
const store = makeStore()
store.create("Learn Node")
assert.deepEqual(store.list(), [{ id: 1, title: "Learn Node", done: false }])
console.log("all tests passed")Learn more from
Played 0 of 2 games
Sign in to save your progress and earn XP.