Un API mic și complet
E timpul să asamblăm piesele: un API de utilizatori cu cele cinci endpoint-uri clasice —
POST /users creare · GET /users listare · GET /users/{id} citirea unuia · PUT /users/{id} înlocuire · DELETE /users/{id} ștergere.
Depozitul este un Map în memorie, astfel încât exemplul rămâne de sine stătător. Node.js rulează handlerele tale unul după altul pe un singur fir de execuție, așa că nimic de aici nu are nevoie de blocări; un server real ar înlocui Map-ul cu o bază de date, iar handlerele nu și-ar schimba forma.
Creare și listare
POST citește JSON-ul primit din corpul cererii, îi dă utilizatorului un id, îl stochează și răspunde cu 201 și înregistrarea creată — id-ul inclus, ca să îl afle și clientul. GET returnează pe toți sub forma unui tablou JSON.
Corpul nu sosește dintr-o dată, așa că un mic ajutor readBody adună bucățile cu for await înainte ca JSON.parse să le vadă.
POST /users și GET /users
Două creări, apoi listarea: fiecare POST răspunde cu 201 și utilizatorul stocat, iar GET-ul final îi arată pe amândoi — starea API-ului a crescut cu fiecare cerere.
Ieșire: program TypeScript și rezultatul capturat
import { createServer, type IncomingMessage, type ServerResponse } from "node:http"
interface UserRecord {
id: number
name: string
}
// Adună tot corpul cererii sub formă de text.
async function readBody(req: IncomingMessage): Promise<string> {
const chunks: Buffer[] = []
for await (const chunk of req) chunks.push(chunk as Buffer)
return Buffer.concat(chunks).toString()
}
const users = new Map<number, UserRecord>()
let nextId = 1
async function createUser(req: IncomingMessage, res: ServerResponse): Promise<void> {
let incoming: { name?: string }
try {
incoming = JSON.parse(await readBody(req))
} catch {
res.writeHead(400).end("JSON invalid\n")
return
}
const user: UserRecord = { id: nextId++, name: incoming.name ?? "" }
users.set(user.id, user)
res.writeHead(201, { "Content-Type": "application/json" })
res.end(JSON.stringify(user))
}
function listUsers(req: IncomingMessage, res: ServerResponse): void {
res.writeHead(200, { "Content-Type": "application/json" })
res.end(JSON.stringify([...users.values()]))
}
async function main(): Promise<void> {
const server = createServer((req, res) => {
const url = new URL(req.url ?? "/", "http://localhost")
if (url.pathname === "/users" && req.method === "POST") {
createUser(req, res)
return
}
if (url.pathname === "/users" && req.method === "GET") {
listUsers(req, res)
return
}
res.writeHead(404).end()
})
await new Promise<void>(resolve => server.listen(0, () => resolve()))
const base = `http://localhost:${(server.address() as { port: number }).port}`
for (const name of ["Ana", "Ben"]) {
const res = await fetch(`${base}/users`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name }),
})
console.log("POST /users ->", res.status, await res.text())
}
const list = await fetch(`${base}/users`)
console.log("GET /users ->", list.status, await list.text())
server.close()
}
main()
Citirea unuia, înlocuire, ștergere
Rutele cu {id} completează setul: citirea răspunde cu 200 sau 404, înlocuirea preia datele noi și păstrează id-ul din URL, ștergerea răspunde cu 204. După ștergere, același GET care funcționa înainte returnează cinstit 404 — resursa nu mai există.
GET, PUT și DELETE pe /users/{id}
Viața completă a unei resurse: o citești, o înlocuiești (răspunsul poartă numele nou Ana Maria), o ștergi și nu reușești să o mai citești — fiecare pas cu codul de status corect.
Ieșire: program TypeScript și rezultatul capturat
import { createServer, type IncomingMessage, type ServerResponse } from "node:http"
interface UserRecord {
id: number
name: string
}
type RouteHandler = (
req: IncomingMessage,
res: ServerResponse,
params: Record<string, string>,
) => void | Promise<void>
interface Route {
method: string
pattern: string
handler: RouteHandler
}
function matchPath(pattern: string, path: string): Record<string, string> | undefined {
const want = pattern.split("/")
const got = path.split("/")
if (want.length !== got.length) return undefined
const params: Record<string, string> = {}
for (let i = 0; i < want.length; i++) {
if (want[i].startsWith("{") && want[i].endsWith("}")) {
params[want[i].slice(1, -1)] = got[i]
} else if (want[i] !== got[i]) {
return undefined
}
}
return params
}
function router(routes: Route[]) {
return (req: IncomingMessage, res: ServerResponse): void => {
const url = new URL(req.url ?? "/", "http://localhost")
for (const route of routes) {
const params = matchPath(route.pattern, url.pathname)
if (params !== undefined && route.method === req.method) {
route.handler(req, res, params)
return
}
}
res.writeHead(404).end()
}
}
async function readBody(req: IncomingMessage): Promise<string> {
const chunks: Buffer[] = []
for await (const chunk of req) chunks.push(chunk as Buffer)
return Buffer.concat(chunks).toString()
}
const users = new Map<number, UserRecord>([[1, { id: 1, name: "Ana" }]])
const routes: Route[] = [
{
method: "GET",
pattern: "/users/{id}",
handler: (req, res, params) => {
res.setHeader("Content-Type", "application/json")
const user = users.get(Number(params.id))
if (user === undefined) {
res.writeHead(404).end(JSON.stringify({ error: "utilizatorul nu a fost găsit" }))
return
}
res.end(JSON.stringify(user))
},
},
{
method: "PUT",
pattern: "/users/{id}",
handler: async (req, res, params) => {
const incoming = JSON.parse(await readBody(req)) as { name: string }
// Înlocuiește datele, dar păstrează id-ul din URL.
const user: UserRecord = { id: Number(params.id), name: incoming.name }
users.set(user.id, user)
res.writeHead(200, { "Content-Type": "application/json" })
res.end(JSON.stringify(user))
},
},
{
method: "DELETE",
pattern: "/users/{id}",
handler: (req, res, params) => {
users.delete(Number(params.id))
res.writeHead(204).end()
},
},
]
async function main(): Promise<void> {
const server = createServer(router(routes))
await new Promise<void>(resolve => server.listen(0, () => resolve()))
const base = `http://localhost:${(server.address() as { port: number }).port}`
async function call(method: string, path: string, payload?: string): Promise<void> {
const res = await fetch(base + path, {
method,
headers: payload === undefined ? {} : { "Content-Type": "application/json" },
body: payload,
})
console.log(`${method.padEnd(6)} ${path} -> ${res.status} ${await res.text()}`.trimEnd())
}
await call("GET", "/users/1")
await call("PUT", "/users/1", JSON.stringify({ name: "Ana Maria" }))
await call("DELETE", "/users/1")
await call("GET", "/users/1")
server.close()
}
main()