This episode designs a consistent JSON API: a uniform payload structure, choosing the right status codes, CORS headers so a frontend can access it, and handling preflight OPTIONS. You test all the headers with curl.

An API that works isn't necessarily an API that's pleasant to use. The experience of consuming an API is determined by three things: a consistent JSON format, the right status codes, and CORS headers that let a frontend browser access it without obstacles.
Episode 10 refines the Express API from episode 9 toward a standard ready to be consumed by frontends and other consumers. You'll design a uniform response structure, choose status codes deliberately, set up CORS including the preflight OPTIONS, and verify everything with curl.
There are two common styles of JSON response design: sending plain data directly, or wrapping it in an envelope:
app.get("/api/artikel", (req, res) => {
res.json({
status: "success",
data: [{ id: 1, judul: "Belajar Node.js" }],
});
});An envelope provides a consistent place for metadata such as pagination and error messages. For small projects, plain data is also valid. What matters most: pick one style and stay consistent across all endpoints — don't mix plain data and envelopes in a single API.
Consistency becomes the contract between the API and its consumers. If one endpoint returns an envelope and another returns a plain array, consumers have to guess the format per endpoint — a source of unnecessary bugs. Write down this convention in the API documentation and follow it on every route.
Status codes are the universal language between server and client. Make sure every endpoint returns a code that describes the actual result:
app.post("/api/artikel", (req, res) => {
const artikel = simpanArtikel(req.body);
res.status(201).json({ status: "success", data: artikel });
});
app.get("/api/artikel/:id", (req, res) => {
const artikel = cariArtikel(req.params.id);
if (!artikel) {
res.status(404).json({ status: "error", error: "Artikel tidak ditemukan" });
return;
}
res.json({ status: "success", data: artikel });
});Notice res.status(201).json(...) for a newly created resource and res.status(404).json(...) for missing data. The same pattern applies to 400 (invalid input), 401 (not logged in), and 403 (no permission), which we'll discuss in episode 11.
Browsers enforce the Same-Origin Policy: JavaScript from http://localhost:5173 can't read responses from http://localhost:3000 without permission. CORS (Cross-Origin Resource Sharing) is that permission mechanism, controlled via response headers.
To choose the allowed origin, add the Access-Control-Allow-Origin header:
app.use((req, res, next) => {
res.setHeader("Access-Control-Allow-Origin", "http://localhost:5173");
res.setHeader("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS");
res.setHeader("Access-Control-Allow-Headers", "Content-Type,Authorization");
if (req.method === "OPTIONS") {
res.sendStatus(204);
return;
}
next();
});The middleware above allows a specific frontend origin. The value "*" allows all origins — practical for public APIs, but avoid it for APIs with authentication. The Allow-Headers header must include Content-Type and Authorization so the frontend can send JSON and tokens.
For non-simple requests — such as POST with Content-Type: application/json or requests with an Authorization header — the browser first sends a preflight OPTIONS request, then waits for permission before the actual request. If the preflight isn't answered with the correct CORS, the real request is cancelled. The if (req.method === "OPTIONS") block above ensures the preflight is answered quickly without being processed as a regular request.
curl -i shows all response headers. Test your CORS with the Origin header:
curl -i -H "Origin: http://localhost:5173" http://localhost:3000/api/artikel
curl -i -X OPTIONS http://localhost:3000/api/artikel \
-H "Origin: http://localhost:5173" \
-H "Access-Control-Request-Method: POST"curl -i -H "Origin: http://localhost:5173" http://localhost:3000/api/artikel must show the Access-Control-Allow-Origin header in the response. The second line simulates the OPTIONS preflight, which must be answered with status 204 and complete CORS headers. If both are correct, your frontend can access the API without issues.
Here's what to take away:
res.status(201) for new resources and 404 for missing data.Allow-Headers header must include Content-Type and Authorization.OPTIONS preflight is answered with status 204 and CORS headers.curl -i and the Origin header.In the next episode, episode 11, we'll discuss basic authentication and authorization — storing passwords with secure hashing, issuing JWT tokens, middleware to verify tokens, and role-based access control. After this, your API has its first security layer.