Calling APIs, Background Jobs and Real-Time

By Syncfusion®, Inc. · Last updated

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.

What you will learn

  • Call other APIs with fetch and check the answer
  • Add timeouts, retries and idempotency keys
  • Cache slow answers
  • Run slow work as a background job
  • Push live updates with Server-Sent Events

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.

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

Client

Express

Worker

202 job 7

status: done

slow work answer now, check later

Answer at once with a job ID. Do the slow work later. The client checks the status.

  • 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.
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

MistakeWrong ideaCorrect ideaWhat goes wrong
Not checking response.okfetch throws on 404 and 500It only throws on network errorsError pages are treated as data
No timeoutThe other server always answersUse AbortSignal.timeoutRequests hang and pile up
Retrying a payment with no keyA retry is always safeSend an idempotency keyStudents are charged twice
Retrying a 400All errors are temporaryRetry only 503, 429 and network errorsWasted calls, same error
Background work with no .catchThe response is sent, so it is fineCatch every background errorAn unhandled rejection crashes the server
Slow work inside the requestUsers can waitAnswer 202, run it as a jobTimeouts and an unhappy user
SSE with no close handlerThe timer stops by itselfClear it on req.on("close")Memory leak as users come and go

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.