What is an API?
An API, short for application programming interface, is a set of addresses that answer programs instead of people. A web page for the bakery menu sends HTML, which a browser draws for someone to read. The bakery's API sends the same menu as data, which a phone app, another website or a script on the bakery's own pages can read and use however it likes.
From the server's point of view the work is almost the same. A request arrives, a route runs, a response goes back. The difference is the format of the body and who reads it. People read pages, programs read data, and data needs a format both sides agree on. On the web that format is almost always JSON.
What does JSON look like?
JSON, JavaScript Object Notation, is text that looks like JavaScript objects and arrays, with stricter rules. Keys must be in double quotes, strings use double quotes, and there are no comments, no trailing commas and no functions. Values can be strings, numbers, true, false, null, arrays and objects, nested as deeply as you like.
Dates are a common surprise. JSON has no date type, so a JavaScript Date becomes a string like "2026-03-14T09:00:00.000Z". A value of undefined is left out entirely. If an app expects a field and it never arrives, check whether the server held undefined there.
[
{ "id": 1, "name": "Sourdough", "price": 5.5, "type": "bread" },
{ "id": 3, "name": "Croissant", "price": 2.5, "type": "pastry" }
]Why use res.json() instead of res.send()?
Press **GET /api/hours** and open Headers and body in the Requests view. The body is valid JSON text, built with JSON.stringify(), but the Content-Type header says text/html. When res.send() gets a string it assumes HTML. Many programs refuse to treat an HTML response as data, and browsers show it as a line of plain text.
res.json(value) does both jobs in one call. It turns the JavaScript value into JSON text and sets Content-Type: application/json; charset=utf-8. Swap it in and send the request again: the header changes, and the Browser view shows the JSON formatted as data.
res.send() with an object or array also sends JSON, because Express notices the type and calls res.json() for you. Writing res.json() says what you mean, and it also handles null, which res.send() turns into an empty body instead of the JSON null.
app.get('/api/hours', (req, res) => {
res.json(hours);
// Content-Type: application/json; charset=utf-8
// {"weekdays":"7:30-18:00","saturday":"8:00-16:00","sunday":null}
});

How should you shape the JSON?
A list of things is an array, and one thing is an object. /api/menu answers with an array of menu items, and /api/menu/3 answers with a single item object. The app that reads them can loop over the first and show fields from the second without guessing.
Use the same field names everywhere. If the list calls a field price, the single item must call it price too, and a later orders API should not switch to cost. Pick one style for names, usually camelCase like memberId, and keep it. Keep numbers as numbers and true or false as booleans, since "5.50" as a string forces every app to convert it before it can add or sort.
Some APIs wrap lists in an object, such as { "items": [...], "count": 4 }, which leaves room to add page numbers later without changing the shape. Either choice works. What hurts is switching between them, or sending an array from one route and a wrapped list from a similar route.
Why does every API path start with /api?
Putting the API under one prefix keeps data and pages apart. /menu is the web page for people and /api/menu is the data for programs, and both can live in the same server. When you later add rules that apply only to the API, such as JSON error pages, rate limits or CORS headers, you can attach them to everything under /api in one line.
The prefix is a convention, nothing more. Express treats /api/menu like any other path. Some sites put the API on a separate address, like api.example.com, for the same reason.
How should an API report errors?
With a status code first, and a JSON body second. A program reads the status before anything else: 200 means use the data, 404 means the thing does not exist, 400 means the request was wrong, and 500 means the server failed. An API that answers every request with 200 and puts the problem in the body forces every app to look inside each response and guess.
The body should still be JSON, because the program asked for JSON. A small object like { "error": "No menu item with that id" } gives the app a message to show or log. Use the same shape for every error in the API, so the code that handles errors in the app only has to be written once.
Remember Express's default pages are HTML. An unknown API path or a thrown error sends Cannot GET /api/cakes or an HTML error page, which an app's JSON.parse() cannot read. A later lesson adds a JSON 404 handler for everything under /api.
app.get('/api/menu/:id', (req, res) => {
const item = menu.find((m) => m.id === Number(req.params.id));
if (!item) {
return res.status(404).json({ error: 'No menu item with that id' });
}
res.json(item);
});How do you read JSON answers in the Requests view?
Every API exchange in the Requests view shows the status and type at a glance: look for 200 and application/json. The body preview shows the start of the JSON, and Headers and body opens the full text. The Browser view formats JSON with indentation, which is easier to read for a long list.
Check the details a program would trip over. Numbers should appear without quotes. Field names should match between the list and the single item. A missing item should say 404 and still be JSON. If the type says text/html on an API route, look for res.send() given a string.
What do AI tools get wrong with JSON APIs?
The most common slip is answering errors with status 200, as in res.json({ success: false }). It looks tidy and breaks every app that checks the status. Another is calling res.json() twice in one route, once for the data and again for a count or a message, which throws ERR_HTTP_HEADERS_SENT because a request gets exactly one response.
Generated APIs also drift in shape: price in one route and cost in another, numbers turned into strings with toFixed(), or a list that sometimes comes back as an object. Read the JSON from two or three routes side by side before you build an app on top of them.
Field notes
- An API is a set of addresses that answer programs with data instead of pages.
- JSON looks like JavaScript objects and arrays, with double quotes around keys and strings.
res.json()sends JSON text with theapplication/jsoncontent type.- Report errors with a status first, such as
404, and a JSON body like{ "error": "..." }.
Easy to get wrong
A string sent with res.send() is labelled text/html. res.json() sends the same text with Content-Type: application/json, which programs and browsers expect for data.
Without a status the error goes out as 200 OK, so the app thinks it received a real menu item. Set the status that matches the problem.
Each request gets one response. The second call throws ERR_HTTP_HEADERS_SENT. Put everything in one value.
A price as a string has to be converted before anyone can add or sort it. Send numbers as numbers and format them where they are shown.
Questions people ask
Can the same route send HTML to browsers and JSON to apps?
Yes, with res.format(), which reads the request's Accept header and picks a format. Separate paths like /menu and /api/menu are easier to test and to reason about, so most small sites use those.
Is JSON the same as a JavaScript object?
No. JSON is text. res.json() turns your object into that text before sending, and the app turns it back into an object with JSON.parse() or response.json(). Functions, undefined and dates do not survive the trip unchanged.
Should an API send private data like email addresses?
Only to people allowed to see it. Anything in a JSON response can be read by whoever made the request. Pick the fields you send on purpose instead of passing whole database rows to res.json().
How does a page on my site use this API?
With fetch('/api/menu') in the browser, then await response.json(). A later lesson builds a page that does exactly that and shows the menu without reloading.