SE Fundamental

The Role of APIs in Modern Web Development

api in web development img
Table of Contents

You have already used a dozen APIs today and you did not notice a single one. The weather on your phone. Signing in with Google. The map inside a food delivery app. The payment screen that was not built by the shop you were buying from. None of those companies built those features. They asked someone else’s software for them.

That asking is what an API does. This guide explains what is really happening on the wire, shows you a live API playground you can click, and gives you working Node.js code both for using someone else’s API and for building your own.

What you will learn (12 minute read):

✓ What an API is, in one diagram instead of one paragraph
✓ The anatomy of a request: method, URL, headers, body, status
✓ A playground where you click an endpoint and see the real request and response
✓ Node.js code for calling an API properly, with errors, timeouts and retries
✓ How to build your own small API with Express
✓ Where to hide your API key, and why the browser is never the place
✓ Free APIs you can practise on tonight, with links

What an API Actually Is

The old restaurant line is still the best one: you do not walk into the kitchen. You tell the waiter what you want, the waiter talks to the kitchen, and food arrives. You never learn how the stove works, and the kitchen never has to trust you with a knife.

An API is that waiter, between two pieces of software:

YOUR APP website, mobile app or another server THE API the agreed set of requests you may make THE SERVICE database, payment system, weather station, AI model request JSON back You never see the kitchen. You only need to know what you may order. That is the whole idea: a stable promise in front of something complicated.

Anatomy of a Request

Nearly every web API works the same way. You send four things and you get two things back. Learn these six pieces and you can use any API in the world.

GET  https://api.example.com/v1/products/42?fields=name,price
base URL — which service endpoint — which collection resource id — which item query string — options
You sendWhat it means
MethodGET to read, POST to create, PUT/PATCH to update, DELETE to remove.
URLThe address of the thing you want, as broken down above.
HeadersExtra information: who you are (Authorization) and what format you are sending (Content-Type).
BodyThe data itself, usually JSON. Only for POST, PUT and PATCH.
You get backWhat it means
Status codeA three-digit verdict: did it work, and if not, whose fault was it?
BodyThe answer, almost always JSON.

Status codes, grouped so you can remember them

RangeMeaningThe ones you will actually meet
2xxIt worked200 OK · 201 Created · 204 No Content
3xxIt moved301 Moved Permanently · 304 Not Modified
4xxYour mistake400 Bad Request · 401 Not logged in · 403 Not allowed · 404 Not found · 429 Too many requests
5xxTheir mistake500 Server Error · 502 Bad Gateway · 503 Service Unavailable

401 or 403? 401 means “I do not know who you are”, so your key or token is missing or wrong. 403 means “I know exactly who you are, and you are not allowed to do that”. That distinction saves hours of debugging.

TRY IT

The API Playground

Click an endpoint below. You will see the exact request that goes out and the response that comes back, including the failures. This is a simulation running in the page, so nothing is sent anywhere.

 

Request
Response

Calling an API from Node.js

Node has fetch built in from version 18 onward, so you do not need to install anything. Here is the simplest possible call:

weather.js
// Open-Meteo needs no API key, so this runs as it is.
const url = 'https://api.open-meteo.com/v1/forecast'
          + '?latitude=30.17&longitude=72.65&current=temperature_2m';

const res = await fetch(url);
const data = await res.json();

console.log(data.current.temperature_2m);
Terminal
$ node weather.js
28.4

Example output. Those coordinates are Burewala; change them for your own city.

Now the version you would actually ship

The code above breaks the moment anything goes wrong. A real call checks the status, gives up if the server hangs, and says something useful when it fails.

api.js  —  a reusable fetch helper
export async function callApi(url, options = {}) {
  const { timeout = 8000, ...rest } = options;

  try {
    const res = await fetch(url, {
      ...rest,
      // give up instead of hanging forever
      signal: AbortSignal.timeout(timeout),
      headers: {
        'Accept': 'application/json',
        'Authorization': `Bearer ${process.env.API_KEY}`,
        ...rest.headers
      }
    });

    // IMPORTANT: fetch does NOT throw on 404 or 500
    if (!res.ok) {
      const body = await res.text();
      throw new Error(`API ${res.status} ${res.statusText}: ${body.slice(0, 200)}`);
    }

    return await res.json();

  } catch (err) {
    if (err.name === 'TimeoutError') {
      throw new Error('The API took too long to answer');
    }
    throw err;
  }
}

The mistake almost every beginner makes: fetch only rejects on a network failure. A 404 or a 500 is still a “successful” fetch as far as JavaScript is concerned. If you skip the res.ok check, you will happily parse an error page as if it were your data, and the bug will appear somewhere else entirely.

Retrying, politely

Servers have bad moments. A 429 means you are going too fast, and a 5xx often fixes itself a second later. Wait a little longer each time rather than hammering away.

retry.js
const sleep = ms => new Promise(r => setTimeout(r, ms));

export async function fetchWithRetry(url, options = {}, tries = 3) {
  for (let attempt = 1; attempt <= tries; attempt++) {
    const res = await fetch(url, options);

    if (res.ok) return res.json();

    // Do not retry our own mistakes (400, 401, 404...)
    const worthRetrying = res.status === 429 || res.status >= 500;
    if (!worthRetrying || attempt === tries) {
      throw new Error(`Request failed with ${res.status}`);
    }

    // 1s, then 2s, then 4s
    await sleep(1000 * Math.pow(2, attempt - 1));
  }
}

Building Your Own API with Express

Using an API is half the job. Here is the other half: a small REST API with all four operations. Create a folder and run npm init -y and npm install express.

server.js
import express from 'express';

const app = express();
app.use(express.json());        // read JSON request bodies

// Pretend database
let products = [
  { id: 42, name: 'Blue Mug', price: 950 },
  { id: 43, name: 'Notebook', price: 420 }
];

// READ the whole list
app.get('/api/products', (req, res) => {
  res.json({ data: products });
});

// READ one item
app.get('/api/products/:id', (req, res) => {
  const item = products.find(p => p.id === Number(req.params.id));
  if (!item) return res.status(404).json({ error: 'Product not found' });
  res.json({ data: item });
});

// CREATE
app.post('/api/products', (req, res) => {
  const { name, price } = req.body;

  // Always validate. Never trust what arrives.
  if (!name || typeof price !== 'number') {
    return res.status(400).json({ error: 'name and numeric price are required' });
  }

  const item = { id: Date.now(), name, price };
  products.push(item);
  res.status(201).json({ data: item });   // 201 = Created
});

// UPDATE
app.patch('/api/products/:id', (req, res) => {
  const item = products.find(p => p.id === Number(req.params.id));
  if (!item) return res.status(404).json({ error: 'Product not found' });

  Object.assign(item, req.body);
  res.json({ data: item });
});

// DELETE
app.delete('/api/products/:id', (req, res) => {
  products = products.filter(p => p.id !== Number(req.params.id));
  res.status(204).end();            // 204 = done, nothing to say
});

app.listen(3000, () => console.log('API on http://localhost:3000'));

To use import instead of require, add "type": "module" to your package.json. Otherwise swap the first line for const express = require('express');.

Test it from a second terminal without writing any front-end at all:

Terminal
# Read everything
curl http://localhost:3000/api/products

# Create something
curl -X POST http://localhost:3000/api/products \
  -H "Content-Type: application/json" \
  -d '{"name":"Desk Lamp","price":2300}'

# Delete it
curl -i -X DELETE http://localhost:3000/api/products/42
Terminal
$ curl http://localhost:3000/api/products
{"data":[{"id":42,"name":"Blue Mug","price":950},
         {"id":43,"name":"Notebook","price":420}]}

$ curl -i -X DELETE http://localhost:3000/api/products/42
HTTP/1.1 204 No Content

Prefer clicking to typing? Postman and Hoppscotch let you build requests in a visual interface, save them and share them with your team. Hoppscotch runs free in the browser with no account.

Where to Keep Your API Key

This is the mistake that costs real money. If your key is in front-end JavaScript, it is public. Anyone can press F12, read it, and spend your quota. Minifying does not hide it.

WRONG — the key is in the browser, so it is public Browser key visible in DevTools Authorization: Bearer sk_live_… Paid third-party API RIGHT — the key never leaves your server Browser no key at all /api/weather Your Node server holds the key in .env Paid third-party API Your server is a thin wrapper. The browser only ever talks to you.
server.js  —  the safe wrapper
// .env           (never commit this file)
// WEATHER_KEY=abcd1234

// .gitignore
// .env
// node_modules/

app.get('/api/weather', async (req, res) => {
  const city = String(req.query.city || '').slice(0, 60);
  if (!city) return res.status(400).json({ error: 'city is required' });

  try {
    const upstream = await fetch(
      `https://api.weather.example/v1?q=${encodeURIComponent(city)}`,
      { headers: { Authorization: `Bearer ${process.env.WEATHER_KEY}` } }
    );

    if (!upstream.ok) {
      return res.status(502).json({ error: 'Weather service unavailable' });
    }

    const data = await upstream.json();

    // Send only what the page needs, not the whole response
    res.json({ city, temp: data.current.temp_c });

  } catch {
    res.status(502).json({ error: 'Weather service unavailable' });
  }
});

If a key ever reaches a public repository, treat it as burnt. Deleting the commit is not enough, because the history keeps it and bots scan GitHub constantly. Go to the provider’s dashboard and revoke the key immediately, then issue a new one.

Keep It Fast: Cache and Rate-Limit

Every external call costs time and often money. If a hundred visitors ask for the same weather in a minute, call the API once.

cache.js  —  a tiny in-memory cache
const cache = new Map();

export async function cached(key, ttlMs, loader) {
  const hit = cache.get(key);
  if (hit && Date.now() < hit.expires) return hit.value;

  const value = await loader();
  cache.set(key, { value, expires: Date.now() + ttlMs });
  return value;
}

// Use it: one real call every 10 minutes per city
const weather = await cached(`weather:${city}`, 10 * 60 * 1000, () =>
  callApi(`https://api.weather.example/v1?q=${city}`)
);

And protect your own API from being hammered:

Terminal + server.js
# npm install express-rate-limit

import rateLimit from 'express-rate-limit';

app.use('/api/', rateLimit({
  windowMs: 60 * 1000,   // one minute
  limit: 60,             // 60 requests per IP per minute
  message: { error: 'Too many requests, slow down' }
}));

REST, GraphQL or WebSockets?

RESTGraphQLWebSockets
ShapeOne URL per resourceOne URL, you describe what you wantA connection that stays open
Best forMost websites and appsComplex screens pulling from many placesChat, notifications, live dashboards
Main benefitSimple, cacheable, understood by everyoneNo over-fetching, one round tripThe server can push to you
Main costSeveral calls to build one screenMore setup, caching is harderHarder to scale and to debug

Start with REST. It covers the overwhelming majority of projects, and every hosting service, browser tool and tutorial supports it. Add GraphQL or WebSockets when you hit a problem REST genuinely cannot solve.

PRACTISE TONIGHT

Free APIs to Build With

The fastest way to learn is to call something real. None of these cost anything, and the first four need no key at all.

JSONPlaceholderFake posts, users and comments. The standard playground for practising GET, POST and DELETE.jsonplaceholder.typicode.com ↗
Open-MeteoReal weather forecasts worldwide, no key, generous free use. Perfect for a first weather app.open-meteo.com ↗
REST CountriesFlags, populations, currencies and borders for every country. Great for search and filter practice.restcountries.com ↗
PokéAPIA huge, well-documented dataset. Ideal for learning pagination and nested resources.pokeapi.co ↗
NASA Open APIsAstronomy picture of the day, Mars rover photos and more. Free key, instant signup.api.nasa.gov ↗
OpenWeatherMapA free tier with an API key, so it is good practice for handling authentication headers.openweathermap.org/api ↗
Public APIs listHundreds of free APIs by category, from football scores to currency rates. Pick one and build.github.com/public-apis ↗
Stripe docsNot free to use in production, but the best written API documentation on the internet. Read it as a model.docs.stripe.com/api ↗

A weekend project that teaches everything: build a small page that takes a city name, calls your own Node endpoint, which calls Open-Meteo, caches the result for ten minutes, and shows a loading state and a friendly error message. That one project covers fetch, error handling, caching, environment variables and server-side proxying.

What Goes Wrong in Production

The old warning about “the dark side of APIs” is fair, but vague. Here is what actually bites, and what to do about each one.

ProblemWhat it looks likeWhat to do
Their service goes downYour page hangs or shows an error, and users blame youSet a timeout, serve cached data, and show a graceful message instead of a blank screen
Breaking changesA field you rely on is renamed or removed one morningUse a versioned endpoint (/v1/), subscribe to their changelog, and never assume a field exists
Rate limitsSudden 429s under real trafficCache aggressively, batch requests, and back off exponentially when you are told to slow down
Surprise billsA loop in your code makes thousands of paid callsSet a spending alert in the provider’s dashboard on day one, and rate-limit your own server
Leaked keysA key in front-end code or pushed to GitHubKeep keys on the server in .env, rotate them regularly, and revoke immediately if exposed
Vague documentationExamples that do not match the real responsesCall the endpoint once and log the raw response, then write your code against what actually arrives
Slow pagesThree API calls happening one after anotherRun independent calls together with Promise.all() instead of awaiting each in turn
Three calls at once instead of one after another
// Slow: 300ms + 300ms + 300ms = 900ms
const user    = await callApi('/api/user');
const orders  = await callApi('/api/orders');
const offers  = await callApi('/api/offers');

// Fast: all three at the same time, about 300ms total
const [user2, orders2, offers2] = await Promise.all([
  callApi('/api/user'),
  callApi('/api/orders'),
  callApi('/api/offers')
]);

Common Questions

What is the difference between an API and a REST API?

“API” is the general idea of one program offering a way in. “REST API” is one popular style of web API, built on normal HTTP methods and URLs that represent resources. Most of what people mean by “an API” on the web today is a REST API returning JSON.

Do I need to know Node to use APIs?

No. You can call an API from browser JavaScript, from any backend language, or from the command line with curl. Node is simply a convenient place to put the calls that need a secret key, since the browser cannot keep secrets.

Why does my browser say “blocked by CORS policy”?

CORS is a browser rule: a page on one domain may not read responses from another domain unless that server allows it. It is not a bug in your code. Either the API has to send permitting headers, or you call it from your own server instead, which is the usual fix and is also where your key belongs.

What is the difference between an API key and OAuth?

An API key identifies your application, like a password for your account with that service. OAuth lets users grant your app limited access to their data without giving you their password. “Sign in with Google” is OAuth.

Should I use axios or fetch?

Either works. fetch is built into modern Node and every browser, so it needs no dependency. Axios adds conveniences like automatic JSON parsing, interceptors and throwing on error statuses. For a small project, fetch plus the helper above is plenty.

How do I version my own API?

Put the version in the path, like /api/v1/products, from the very first release. When you need to make a breaking change, publish /v2/ and keep /v1/ running for a while. Your users will thank you, and so will you.

Read next: Cookies and Sessions in Web Development explains how logins work when the client is a browser, which is the other half of this topic.

Final Thoughts

An API is just a promise: send this, get that back. Once you can read a status code, check res.ok, and keep your key on the server, every API in the world works the same way. The documentation changes; the shape does not.

Open a terminal tonight, write those four lines of Node against Open-Meteo, and print the temperature in your own city. That is your first integration, and everything after it is the same idea with more steps. Which API are you going to try first? Tell me in the comments. I read every one.

If this helped, share it with someone who is still copying API code without knowing what it does.

Share this article

Muhammad Azeem

Written by

Muhammad Azeem

Articles by Muhammad Azeem on azeemtechstudio.com.

Stay Updated

Get the latest coding tutorials, web development tips, AI tools, and technology articles delivered to your inbox.

No spam — we only use your email to send new articles.

Join the Discussion

Your email address will not be published. Required fields are marked *

About AzeemTechStudio

AzeemTechStudio Learn about programming and Web Development

Practical tutorials, programming tips and insights on Laravel, PHP, Docker, Linux, JavaScript, AI and modern web development.

Learn more