# Calling APIs, Background Jobs and Real-Time

Course: [Express.js](https://www.devtutorials.ai/learn/expressjs) · Section: Advanced HTTP
Source: https://www.devtutorials.ai/learn/expressjs/advanced-http/calling-apis-jobs-realtime

> Calling other APIs with fetch, checking response.ok, timeouts, retries with backoff, idempotency keys, caching, background jobs with 202 Accepted, fire-and-forget work, Server-Sent Events and WebSockets.

## Calling APIs, Background Jobs and Real-Time

Real servers rarely work alone. They call other services, do slow work later, and push live updates.

- **Calling an API** = your server sends its own request to another server with `fetch`.
- **Timeout** = give up if the other server takes too long.
- **Retry** = try again when a failure is temporary.
- **Background job** = slow work that runs after the response is sent.
- **Real-time** = the server sends updates without the browser asking again.

**Simple lesson:** Your server is also a client. Treat other servers as slow and unreliable.

### A Real-World Example

Think about the canteen counter at lunch.

1. Priya orders a dosa. The counter calls the kitchen to check stock. That is an API call.
2. If the kitchen does not answer in 10 seconds, the counter says "try again soon". That is a timeout.
3. The counter gives Priya a token number right away. The dosa is made later. That is a background job.
4. The screen above the counter shows "Token 42 ready". That is a real-time update.

In this lesson, you will learn:

1. How to call other APIs with `fetch`.
2. How to add timeouts, retries and idempotency keys.
3. How to cache answers from slow services.
4. How to run slow work in the background and answer `202 Accepted`.
5. How to push live updates with Server-Sent Events, and where WebSockets fit.

The labs use pretend services such as `api.weather.lab`, `pay.lab` and `mail.lab`. They behave like real ones, but never leave your browser.

## Calling Another API with fetch

Node.js 18 and later has `fetch` built in. No package needed.

- `await fetch(url)` sends the request and gives a **Response**.
- `response.ok` is `true` for 2xx status codes. `fetch` does **not** throw for 404 or 500.
- `await response.json()` reads the body as JSON.
- `fetch` **throws** only when the request could not be sent at all, such as a wrong host name.
- Send JSON with `method`, `headers` and `body: JSON.stringify(data)`.

- `502 Bad Gateway` means "another server I depend on failed". Use it when an outside call breaks.
- `encodeURIComponent` keeps spaces and symbols in the city name safe inside the URL.

**Simple lesson:** Check `response.ok` yourself. Catch errors for hosts that cannot be reached.

## Timeouts, Retries and Idempotency

Outside services can be slow or busy. Plan for it.

- **Timeout:** pass `signal: AbortSignal.timeout(1000)`. After 1 second, `fetch` throws a `TimeoutError`.
- Answer **504 Gateway Timeout** when the other server was too slow.
- **Retry** only temporary failures: 503, 429 and network errors. Never retry a 400.
- Wait a little longer between tries. This is called **backoff**.
- **Idempotency key** = a unique ID sent with a payment. If you retry, the payment service sees the same key and does not charge twice.

- The second payment returns the **same** charge ID. Priya is charged once.
- In real apps, packages such as `p-retry` handle retries and backoff for you.

Never retry a payment or an order without an idempotency key. A retry after a timeout can charge the student twice.

**Simple lesson:** Always set a timeout. Retry only safe or keyed requests.

## Caching Answers

Calling a slow service on every request wastes time.

- **Cache** = keep a copy of an answer for a short time.
- Store the value and the time it expires.
- If the copy is still fresh, return it without calling the service.
- Choose a short life. Weather can be 10 minutes old. A bank balance cannot.
- Real apps share one cache between servers with **Redis**.

- Look at the `X-Cache` header: MISS, HIT, then MISS again after the copy expires.

**Simple lesson:** Cache slow answers for a short, safe time.

## Background Jobs

Some work is too slow to finish inside one request: making a report, sending 500 emails, resizing photos.

<figure className="xfig ngc-fig">
<svg viewBox="0 0 560 170" role="img" aria-labelledby="xfig-jobs" style={{ width: "100%", maxWidth: 560, height: "auto" }}>
  <title id="xfig-jobs">Animation: the client gets 202 and a job ID at once, a worker does the slow job, then the client checks the status and sees done</title>
  <g style={{ fontFamily: "var(--sans)", fontSize: 13, fontWeight: 700 }}>
    <rect x="15" y="20" width="110" height="130" rx="12" style={{ fill: "var(--ngc-blue-soft)", stroke: "var(--ngc-blue)", strokeWidth: 2 }} />
    <text x="70" y="45" textAnchor="middle" style={{ fill: "var(--ngc-blue)" }}>Client</text>
    <rect x="225" y="20" width="110" height="130" rx="12" style={{ fill: "var(--ngc-green-soft)", stroke: "var(--ngc-green)", strokeWidth: 2 }} />
    <text x="280" y="45" textAnchor="middle" style={{ fill: "var(--ngc-green)" }}>Express</text>
    <rect x="435" y="20" width="110" height="130" rx="12" style={{ fill: "var(--ngc-orange-soft)", stroke: "var(--ngc-orange)", strokeWidth: 2 }} />
    <text x="490" y="45" textAnchor="middle" style={{ fill: "var(--ngc-orange)" }}>Worker</text>
    <rect x="455" y="95" width="70" height="12" rx="6" style={{ fill: "var(--panel)", stroke: "var(--ngc-orange)" }} />
    <rect x="455" y="95" width="0" height="12" rx="6" style={{ fill: "var(--ngc-orange)" }}>
      <animate attributeName="width" values="0;0;70;70" keyTimes="0;0.3;0.7;1" dur="8s" repeatCount="indefinite" />
    </rect>
  </g>
  <g style={{ fontFamily: "var(--mono)", fontSize: 11, fontWeight: 700 }}>
    <g opacity="0">
      <rect x="-60" y="-12" width="120" height="22" rx="11" style={{ fill: "var(--ngc-green)" }} />
      <text x="0" y="3" textAnchor="middle" style={{ fill: "#fff" }}>202 job 7</text>
      <animateMotion path="M175 75 L175 75" dur="8s" repeatCount="indefinite" />
      <animate attributeName="opacity" values="0;1;1;0;0" keyTimes="0;0.1;0.25;0.27;1" dur="8s" repeatCount="indefinite" />
    </g>
    <g opacity="0">
      <rect x="-60" y="-12" width="120" height="22" rx="11" style={{ fill: "var(--ngc-blue)" }} />
      <text x="0" y="3" textAnchor="middle" style={{ fill: "#fff" }}>status: done</text>
      <animateMotion path="M175 125 L175 125" dur="8s" repeatCount="indefinite" />
      <animate attributeName="opacity" values="0;0;1;1;0" keyTimes="0;0.75;0.77;0.95;1" dur="8s" repeatCount="indefinite" />
    </g>
  </g>
  <g style={{ fontFamily: "var(--sans)", fontSize: 11, fill: "var(--muted)" }}>
    <text x="385" y="85" textAnchor="middle">slow work</text>
    <text x="175" y="165" textAnchor="middle">answer now, check later</text>
  </g>
</svg>
<figcaption>Answer at once with a job ID. Do the slow work later. The client checks the status.</figcaption>
</figure>

- Answer **202 Accepted** with a job ID straight away.
- Give a status URL such as `/api/jobs/7`. The client checks it until the job is `done`.
- **Fire-and-forget:** for small extras like a confirmation email, start the work and do not `await` it. Always add `.catch`, or a failure crashes the server.
- In production, use a **job queue** such as BullMQ with Redis. Jobs survive restarts and can run on other machines.
- For jobs on a schedule, such as "clear old orders at midnight", use `node-cron` or your host's scheduler.

**Simple lesson:** Answer fast with 202. Do slow work in the background, and always catch its errors.

## Live Updates with Server-Sent Events

How does the canteen screen show "Token 42 ready" without refreshing?

- **Polling** = the browser asks again every few seconds. Simple, but wasteful.
- **Server-Sent Events (SSE)** = one long response. The server keeps writing new lines.
- Set `Content-Type: text/event-stream`, then `res.write("data: ...\n\n")` for each event.
- The browser reads it with `new EventSource("/api/tokens/stream")`, and reconnects by itself.
- Listen for `req.on("close")` to stop the timer when the user leaves.

- The lab watches the stream for 1.6 seconds, then disconnects. See `screen disconnected` in the terminal.

### WebSockets

- **WebSocket** = a two-way connection. Both sides can send at any time.
- Use it for chat, games and live editing. SSE is enough when only the server talks.
- Express does not handle WebSockets itself. Add the `ws` or **Socket.IO** package on the same HTTP server.

```js
import { createServer } from "node:http";
import { Server } from "socket.io";

const server = createServer(app);
const io = new Server(server);

io.on("connection", (socket) => {
  socket.on("order:placed", (order) => io.emit("kitchen:new-order", order));
});

server.listen(3000);
```

Easy way to remember: **SSE is a radio** (server talks, you listen). **WebSocket is a phone call** (both talk).

**Simple lesson:** SSE for one-way live updates, WebSockets for two-way talk.

## Common mistakes

<div className="tscroll">

| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| Not checking `response.ok` | `fetch` throws on 404 and 500 | It only throws on network errors | Error pages are treated as data |
| No timeout | The other server always answers | Use `AbortSignal.timeout` | Requests hang and pile up |
| Retrying a payment with no key | A retry is always safe | Send an idempotency key | Students are charged twice |
| Retrying a 400 | All errors are temporary | Retry only 503, 429 and network errors | Wasted calls, same error |
| Background work with no `.catch` | The response is sent, so it is fine | Catch every background error | An unhandled rejection crashes the server |
| Slow work inside the request | Users can wait | Answer 202, run it as a job | Timeouts and an unhappy user |
| SSE with no `close` handler | The timer stops by itself | Clear it on `req.on("close")` | Memory leak as users come and go |

</div>

## Check Yourself

## Exercises

## What to Remember

- `fetch` only throws on network errors. Always check `response.ok`.
- Set a timeout with `AbortSignal.timeout`. Answer 502 or 504 when outside services fail.
- Retry only temporary failures, with backoff, and use idempotency keys for payments.
- Cache slow answers for a short, safe time.
- Slow work: answer 202 with a job ID, run it in the background, catch every error.
- SSE pushes one-way live updates. WebSockets (`ws`, Socket.IO) are for two-way talk.

Next: **Logging and Debugging**. You will see what your server is doing and find bugs faster.
