tfs-demos

API Design Guide

These notes are not intended as a comprehensive guide to the topic. Their purpose is to guide you through the main areas you should learn about, with resources provided for further exploration. The goal is for you to learn enough to complete the associated challenge.

These notes explain what a web API is and why you might build one before moving to how to design requests, responses, URLs, and errors.

Examples use JSON and a simple space theme with planets, moons, and missions.

Helpful references


What is an API

An API is a contract that lets one piece of software ask another to do something or to provide information

For web APIs the contract travels over the internet using HTTP which is the same protocol your browser uses to load web pages

Plain language

Why teams build an API

A good API is predictable, well documented, and hard to misuse

Read more


The client and the server

A useful mental model is a polite conversation

Read more


Why REST

REST is a set of ideas for building web APIs that fit neatly with HTTP

It treats the things in your system as resources such as planets and moons and gives them stable URLs

You manipulate those resources using standard HTTP methods such as GET and POST

Why REST is popular

Read more


The building blocks of HTTP

Requests

Every request has the same basic shape

METHOD PATH HTTP/VERSION

Requests can also have headers, which provide information about the request:

METHOD PATH HTTP/VERSION
Header-Name: value
Another-Header: value

Requests can also have an optional body:

METHOD PATH HTTP/VERSION

Header-Name: value
Another-Header: value

optional request body

Responses

Every response has the same basic shape

HTTP/VERSION STATUS_CODE Reason-Phrase
Header-Name: value
Another-Header: value

optional response body

Key terms

Read more


HTTP methods with their meaning

Method Plain English meaning Typical use
GET Tell me about this thing or list of things Read a resource or collection
POST Create a new thing under this URL or run a command Create in a collection
PUT Replace the thing at this exact URL Full update or create at a known URL
PATCH Change part of the thing at this URL Partial update
DELETE Remove the thing at this URL Delete

Two useful properties

Read more


Status codes as a shared vocabulary

A status code tells the client what happened

Success

Client problems

Server problems

Read more


Representations and content negotiation

A resource can be represented in different formats

We will use JSON which is simple to read and write

Tell the server what you are sending and what you can accept

Keep JSON simple and stable. Use predictable field names and types.

Use ISO 8601 date strings such as 1610-01-07

Read more


An example scenario

We will design an API that stores information about planets, their moons, and space missions that visit those planets

This scenario gives clear examples of common API patterns


Resource modelling and URLs

Think in nouns, not verbs. Nouns become resources, and resources get URLs

Top level resources have their own URL paths

Use predictable, hierarchical paths

Simple path rules

Read more


Requests and responses example

Create a planet

Request

POST /planets
Content-Type: application/json
Accept: application/json

{
  "name": "Jupiter",
  "discovered_at": "1610-01-07"
}

Response

HTTP/1.1 201 Created
Location: /planets/1
Content-Type: application/json

{
  "id": 1,
  "name": "Jupiter",
  "discovered_at": "1610-01-07"
}

List moons for a planet with filtering and pagination

Request

GET /planets/1/moons?sort=name&order=asc&page=2&per_page=3
Accept: application/json

Response

HTTP/1.1 200 OK
Content-Type: application/json

{
  "data": [
    { "id": 7, "name": "Callisto" },
    { "id": 8, "name": "Europa" },
    { "id": 9, "name": "Ganymede" }
  ],
  "meta": { "page": 2, "per_page": 3, "total": 12 }
}

Read more


Designing collection endpoints

Use query parameters for common collection concerns

Read more


Errors that help users fix mistakes

Use a consistent format for errors, for example:

HTTP/1.1 422 Unprocessable Entity
Content-Type: application/problem+json

{
  "title": "Validation failed",
  "status": 422,
  "detail": "One or more fields are invalid",
  "errors": [
    { "field": "name", "message": "Name is required" },
    { "field": "discovered_at", "message": "Use ISO date format YYYY-MM-DD" }
  ]
}

Use status codes:

Read more


Authentication and authorisation

Start simple and keep secrets out of URLs

Common approaches

Return 401 for missing or invalid credentials and 403 when authenticated but not allowed

Read more


Documentation that developers can run

Write the contract down in OpenAPI so tools can read it

Benefits

A tiny OpenAPI fragment

openapi: 3.1.0
info:
  title: Space API
  version: 1.0.0
servers:
  - url: https://api.example.com
paths:
  /planets:
    get:
      summary: List planets
      parameters:
        - in: query
          name: page
          schema: { type: integer, minimum: 1, default: 1 }
        - in: query
          name: per_page
          schema: { type: integer, minimum: 1, maximum: 100, default: 20 }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Planet'
  /planets/{planet_id}:
    get:
      summary: Get a planet
      parameters:
        - in: path
          name: planet_id
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: OK
components:
  schemas:
    Planet:
      type: object
      properties:
        id: { type: integer }
        name: { type: string }
        discovered_at: { type: string, format: date }

Read more


A minimal service outline in Node.js

This is a sketch to show where HTTP concerns live. It reads like Express pseudo-code

Responsibilities

// routes
router.get('/planets', listPlanets)
router.post('/planets', authenticate, createPlanet)
router.get('/planets/:id', getPlanet)
router.patch('/planets/:id', authenticate, updatePlanet)
router.delete('/planets/:id', authenticate, deletePlanet)

// handler shapes
async function listPlanets(req, res) {
  const { page = 1, per_page = 20, sort = 'name', order = 'asc', fields } = req.query
  const result = await db.planets.findMany({ page, per_page, sort, order, fields })
  res.status(200).json({
    data: result.rows,
    meta: { page, per_page, total: result.total }
  })
}

async function createPlanet(req, res) {
  const planet = await db.planets.create(req.body)
  res.status(201).json(planet)
}

Read more


Security basics

Read more


Practical conventions that keep APIs consistent

Read more


Exercises

  1. Design the URLs and methods for a missions resource. Include list, create, get one, update name, and delete
  2. Add filtering to list planets by discovery century. Choose parameter names and write example URLs
  3. Design a complete request and response for creating a moon
  4. Extend the OpenAPI fragment to include GET /planets/{planet_id}/moons with response schema and an example