web-dev4 min read

RESTful API Tutorial: Learn Web Services from Scratch (2026)

RESTful API Tutorial: Learn Web Services from Scratch (2026)

Published:  |  Category: Web Dev  |  Reading time: ~15 min
RESTful API Tutorial: Learn Web Services from Scratch (2026)

REST (Representational State Transfer) is an architectural style for designing networked applications. Introduced by Roy Fielding in his 2000 PhD dissertation, REST defines constraints that produce scalable, stateless, and cacheable APIs. RESTful APIs use standard HTTP methods — GET, POST, PUT, PATCH, DELETE — to perform CRUD operations on resources identified by URLs.

REST has become the dominant API design style due to its simplicity and reliance on familiar HTTP semantics. A well-designed REST API is intuitive, predictable, and easy to evolve.

HTTP Methods and Status Codes

Each HTTP method maps to a CRUD operation: GET retrieves, POST creates, PUT replaces, PATCH modifies, DELETE removes. HEAD returns headers without body. GET and DELETE should be idempotent.

Status codes: 200 OK (reads/updates), 201 Created (new resource), 204 No Content (deletions). Client errors: 400 (bad request), 401 (unauthorized), 403 (forbidden), 404 (not found), 409 (conflict), 422 (validation). Server errors: 500.

GET    /api/users          # List all users
GET    /api/users/42       # Get user with ID 42
POST   /api/users          # Create a new user
PATCH  /api/users/42       # Partially update user 42
DELETE /api/users/42       # Delete user 42

HTTP/1.1 201 Created
Content-Type: application/json
Location: /api/users/43

{
  "id": 43,
  "name": "Alice",
  "email": "alice@example.com",
  "createdAt": "2026-07-08T12:00:00Z"
}

Resource Naming Conventions

URLs represent resources as nouns, not actions. Use plural nouns (/users, not /getUser). Nest related resources hierarchically: /users/42/posts. Query parameters handle filtering, sorting, and pagination.

Avoid verbs in URLs — PATCH is preferred over POST for updates. Field selection and embedding improve API flexibility.

GET    /api/posts                    # All posts
GET    /api/posts/7                  # Single post
POST   /api/posts                    # Create post

GET /api/posts?page=2&limit=25&sort=created_at&order=desc
GET /api/posts?fields=id,title,summary&include=author

{
  "data": [...],
  "meta": { "page": 2, "limit": 25, "total": 143 },
  "links": { "self": "/api/posts?page=2", "next": "/api/posts?page=3" }
}

CRUD Operations Implementation

CRUD follows consistent patterns: list (GET), detail (GET /:id), create (POST), update (PUT/PATCH), delete (DELETE). Request bodies are JSON. Create returns 201 with Location header.

Error responses include code, message, and requestId. Validation errors return per-field messages. Idempotent operations return 200 or 204.

app.get('/api/posts', async (req, res) => {
  const { page = 1, limit = 20 } = req.query;
  const offset = (page - 1) * limit;
  const [posts, total] = await Promise.all([
    db.posts.findMany({ skip: offset, take: limit }),
    db.posts.count()
  ]);
  res.json({ data: posts, meta: { page: Number(page), limit: Number(limit), total } });
});

app.post('/api/posts', async (req, res) => {
  const post = await db.posts.create({ data: req.body });
  res.status(201).location(`/api/posts/${post.id}`).json({ data: post });
});

Authentication and Authorization

REST APIs are stateless — every request includes credentials. Common methods: API keys, Bearer tokens (JWT), and OAuth 2.0. JWTs encode identity and claims in a signed token, eliminating server-side session storage.

Authorization uses RBAC with roles. Resource-level checks verify ownership. Middleware extracts and verifies tokens, attaching the decoded user to the request context.

const jwt = require('jsonwebtoken');

function authenticate(req, res, next) {
  const header = req.headers.authorization;
  if (!header || !header.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'Missing token' });
  }
  try {
    req.user = jwt.verify(header.split(' ')[1], process.env.JWT_SECRET);
    next();
  } catch (err) {
    return res.status(403).json({ error: 'Invalid or expired token' });
  }
}

app.delete('/api/posts/:id', authenticate, authorize('admin'), handler);

API Versioning Strategies

Three strategies: URI versioning (/api/v1/posts), header versioning (Accept header with version), parameter versioning (?version=2). URI versioning is most common — visible, cacheable, easy to route at the proxy level.

Maintain old versions with deprecation windows. Use Sunsent and Deprecation response headers. Additive changes should be backward-compatible to avoid version bumps.

app.use('/api/v1', v1Router);
app.use('/api/v2', v2Router);

function deprecationCheck(req, res, next) {
  if (req.path.startsWith('/api/v1')) {
    res.set('Deprecation', 'true');
    res.set('Sunset', 'Sat, 01 Nov 2026 00:00:00 GMT');
    res.set('Link', '; rel="successor-version"');
  }
  next();
}

Documentation with OpenAPI

OpenAPI Specification is the industry standard for documenting REST APIs. An OpenAPI document describes endpoints, parameters, schemas, and authentication in YAML or JSON. Swagger UI renders interactive documentation for testing endpoints directly from the browser.

Generate specs automatically with swagger-jsdoc or drf-spectacular. Define every response code with schemas. Use $ref for reusable components to avoid duplication.

openapi: 3.0.3
info:
  title: Blog API
  version: 2.0.0
paths:
  /api/v2/posts:
    get:
      summary: List all posts
      parameters:
        - name: page
          in: query
          schema: { type: integer, default: 1 }
      responses:
        '200':
          description: Paginated list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Post'
components:
  schemas:
    Post:
      type: object
      properties:
        id: { type: integer }
        title: { type: string }

Frequently Asked Questions

What is the difference between REST and GraphQL?

REST uses fixed endpoints with predetermined data; GraphQL uses one endpoint where clients specify needed data. REST benefits from HTTP caching; GraphQL avoids over-fetching.

How do I handle errors consistently?

Use a standard error envelope with error, code, message, and details fields. Return appropriate status codes. Add a requestId for tracing.

Should I use PUT or PATCH?

Use PUT for full replacement. Use PATCH for partial updates. PATCH is more efficient for large resources and preferred for modern REST APIs.

What is HATEOAS?

HATEOAS includes links in responses so clients discover actions dynamically. Most practical APIs skip full HATEOAS for documented OpenAPI specs.

Originally published on Ayodhyyya. Last updated June 1, 2026.