Vad är REST?

REST (Representational State Transfer) är en arkitekturstil för att designa nätverksapplikationer. En RESTful API använder HTTP-requests för att utföra CRUD-operationer (Create, Read, Update, Delete) på resurser.

REST Principer

1. Client-Server Architecture

Klient och server är separata - de kan utvecklas oberoende.

2. Stateless

Varje request från klient till server måste innehålla all information som behövs. Servern lagrar ingen session state.

3. Cacheable

Responses ska definiera om de kan cachas eller inte.

4. Uniform Interface

Konsekvent interface mellan komponenter.

5. Layered System

Klienten ska inte behöva veta om den pratar direkt med servern eller genom intermediaries.

Resource Naming

Använd Substantiv, Inte Verb

# ✅ Bra
GET    /users
GET    /users/123
POST   /users
PUT    /users/123
DELETE /users/123

# ❌ Dåligt
GET    /getUsers
POST   /createUser
PUT    /updateUser/123
DELETE /deleteUser/123

Plural Form

# ✅ Konsekvent plural
/users
/products
/orders

# ❌ Blandning
/user
/products
/order

Hierarkiska Relationer

# ✅ Visa relationer tydligt
GET /users/123/orders
GET /users/123/orders/456

# Läs som: "Get order 456 belonging to user 123"

Använd Kebab-Case

# ✅ Bra
/api/user-profiles
/api/order-items

# ❌ Undvik
/api/user_profiles  (snake_case)
/api/userProfiles   (camelCase)

HTTP Methods

GET - Hämta Data

GET /api/users
GET /api/users/123

Egenskaper:

  • Safe (ändrar inte state)
  • Idempotent (samma resultat varje gång)
  • Cacheable

POST - Skapa Resurs

POST /api/users
Content-Type: application/json

{
  "name": "John Doe",
  "email": "john@example.com"
}

Response:

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

{
  "id": 124,
  "name": "John Doe",
  "email": "john@example.com",
  "createdAt": "2024-12-07T10:00:00Z"
}

PUT - Uppdatera (Ersätt) Resurs

PUT /api/users/123
Content-Type: application/json

{
  "name": "John Smith",
  "email": "john.smith@example.com"
}

Egenskaper:

  • Idempotent
  • Ersätter hela resursen

PATCH - Partiell Uppdatering

PATCH /api/users/123
Content-Type: application/json

{
  "email": "newemail@example.com"
}

Egenskaper:

  • Uppdaterar bara angivna fält
  • Inte alltid idempotent

DELETE - Ta bort Resurs

DELETE /api/users/123

Response:

HTTP/1.1 204 No Content

HTTP Status Codes

2xx Success

200 OK              - Lyckad GET, PUT, PATCH
201 Created         - Lyckad POST
204 No Content      - Lyckad DELETE (ingen body)

3xx Redirection

301 Moved Permanently  - Resurs flyttad permanent
304 Not Modified       - Cachad version är giltig

4xx Client Errors

400 Bad Request          - Ogiltig syntax
401 Unauthorized         - Authentication krävs
403 Forbidden            - Inte behörighet
404 Not Found            - Resurs finns inte
405 Method Not Allowed   - HTTP method inte tillåten
409 Conflict             - Konflikt (t.ex. duplicate)
422 Unprocessable Entity - Validation error
429 Too Many Requests    - Rate limit

5xx Server Errors

500 Internal Server Error  - Server error
502 Bad Gateway           - Upstream error
503 Service Unavailable   - Server overloaded

Request & Response Format

Content Negotiation

# Request
GET /api/users/123
Accept: application/json

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

{
  "id": 123,
  "name": "John Doe"
}

Konsekvent JSON Structure

{
  "data": {
    "id": 123,
    "type": "user",
    "attributes": {
      "name": "John Doe",
      "email": "john@example.com"
    }
  },
  "meta": {
    "timestamp": "2024-12-07T10:00:00Z"
  }
}

Error Responses

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation failed",
    "details": [
      {
        "field": "email",
        "message": "Invalid email format"
      }
    ]
  }
}

Versioning

URL Versioning (Vanligast)

# ✅ Rekommenderat
/api/v1/users
/api/v2/users

Header Versioning

GET /api/users
Accept: application/vnd.myapi.v2+json

Query Parameter (Mindre vanligt)

/api/users?version=2

Best Practice: URL versioning är enklast och tydligast.

Filtering, Sorting & Pagination

Filtering

GET /api/users?role=admin
GET /api/users?status=active&role=admin
GET /api/products?price_min=10&price_max=100

Sorting

GET /api/users?sort=name
GET /api/users?sort=-createdAt  # Descending
GET /api/users?sort=name,-age   # Multiple fields

Pagination

Offset-based:

GET /api/users?limit=20&offset=40

Cursor-based (Bättre):

GET /api/users?limit=20&cursor=eyJpZCI6MTIzfQ

Response:

{
  "data": [...],
  "pagination": {
    "total": 1000,
    "limit": 20,
    "offset": 40,
    "nextCursor": "eyJpZCI6MTQzfQ",
    "hasMore": true
  }
}

Field Selection

Låt klienter välja vilka fält de vill ha:

GET /api/users?fields=id,name,email

Response:

{
  "data": [
    {
      "id": 123,
      "name": "John Doe",
      "email": "john@example.com"
    }
  ]
}

Nästlade Resurser

Shallow Nesting (Rekommenderat)

# ✅ Max 2 nivåer
GET /api/users/123/orders

# ✅ För specifika resurser, använd direkta endpoints
GET /api/orders/456

Deep Nesting (Undvik)

# ❌ För djupt
GET /api/users/123/orders/456/items/789

Authentication & Authorization

Bearer Token (JWT)

GET /api/users/me
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

API Keys

GET /api/users
X-API-Key: your-api-key-here

OAuth 2.0

För third-party access:

1. Authorization request
2. User grants permission
3. Server issues access token
4. Client uses token

Rate Limiting

Headers

X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1701950400

Response när limit nås

HTTP/1.1 429 Too Many Requests
Retry-After: 3600

{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Too many requests. Try again in 1 hour."
  }
}

HATEOAS

Hypermedia As The Engine Of Application State:

{
  "id": 123,
  "name": "John Doe",
  "links": {
    "self": "/api/users/123",
    "orders": "/api/users/123/orders",
    "profile": "/api/users/123/profile"
  }
}

Caching

ETag

# Response
HTTP/1.1 200 OK
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"

# Next request
GET /api/users/123
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"

# Response if not modified
HTTP/1.1 304 Not Modified

Cache-Control

Cache-Control: max-age=3600, public
Cache-Control: no-cache
Cache-Control: no-store  # Känslig data

Idempotency

För säkra retries:

POST /api/orders
Idempotency-Key: a1b2c3d4-e5f6-7890-abcd-ef1234567890

{
  "items": [...]
}

Server sparar key och returnerar samma response vid retry.

Security Best Practices

1. HTTPS Only

# Force HTTPS
server {
  listen 80;
  return 301 https://$host$request_uri;
}

2. Input Validation

// ✅ Validera alla inputs
const schema = z.object({
  email: z.string().email(),
  age: z.number().min(0).max(150)
});

const result = schema.safeParse(req.body);
if (!result.success) {
  return res.status(400).json({
    error: result.error
  });
}

3. SQL Injection Prevention

// ❌ FARLIGT
db.query(`SELECT * FROM users WHERE id = ${userId}`);

// ✅ Använd prepared statements
db.query('SELECT * FROM users WHERE id = ?', [userId]);

4. Rate Limiting

const rateLimit = require('express-rate-limit');

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100 // 100 requests per window
});

app.use('/api/', limiter);

5. CORS

app.use(cors({
  origin: 'https://yourdomain.com',
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization']
}));

Documentation

OpenAPI/Swagger

openapi: 3.0.0
info:
  title: My API
  version: 1.0.0

paths:
  /users:
    get:
      summary: List users
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/User'

Testing

Unit Tests

describe('GET /api/users/:id', () => {
  it('should return user', async () => {
    const res = await request(app)
      .get('/api/users/123')
      .expect(200);
    
    expect(res.body).toHaveProperty('id', 123);
  });
  
  it('should return 404 for non-existent user', async () => {
    await request(app)
      .get('/api/users/999')
      .expect(404);
  });
});

Monitoring & Logging

Request Logging

app.use((req, res, next) => {
  console.log({
    method: req.method,
    path: req.path,
    query: req.query,
    ip: req.ip,
    userAgent: req.get('User-Agent')
  });
  next();
});

Health Check Endpoint

app.get('/health', (req, res) => {
  res.json({
    status: 'healthy',
    timestamp: new Date().toISOString(),
    uptime: process.uptime(),
    services: {
      database: 'connected',
      redis: 'connected'
    }
  });
});

Common Mistakes

1. Verb in URLs

# ❌ Dåligt
POST /api/createUser
GET /api/getUsers

# ✅ Bra
POST /api/users
GET /api/users

2. Wrong Status Codes

// ❌ Dåligt
res.status(200).json({ error: 'User not found' });

// ✅ Bra
res.status(404).json({ error: 'User not found' });

3. Inconsistent Naming

# ❌ Inkonsekvent
/api/users
/api/Products
/api/order_items

# ✅ Konsekvent
/api/users
/api/products
/api/order-items

Performance Optimization

1. Database Queries

// ❌ N+1 problem
const users = await User.findAll();
for (const user of users) {
  user.orders = await Order.findAll({ userId: user.id });
}

// ✅ Använd joins
const users = await User.findAll({
  include: [Order]
});

2. Response Compression

const compression = require('compression');
app.use(compression());

3. Caching

const redis = require('redis');
const client = redis.createClient();

app.get('/api/users/:id', async (req, res) => {
  const cached = await client.get(`user:${req.params.id}`);
  
  if (cached) {
    return res.json(JSON.parse(cached));
  }
  
  const user = await User.findById(req.params.id);
  await client.setex(`user:${req.params.id}`, 3600, JSON.stringify(user));
  
  res.json(user);
});

Verktyg

Testa och bygg APIs:

Checklista

Design

  • RESTful resource naming
  • Korrekt användning av HTTP methods
  • Korrekt användning av status codes
  • Konsekvent error handling
  • Versioning strategy

Security

  • HTTPS enforced
  • Authentication implemented
  • Authorization checks
  • Input validation
  • Rate limiting
  • CORS configured

Performance

  • Database query optimization
  • Caching strategy
  • Response compression
  • Pagination implemented

Documentation

  • API documentation (OpenAPI)
  • Example requests/responses
  • Error codes documented
  • Authentication documented

Monitoring

  • Logging implemented
  • Health check endpoint
  • Error tracking
  • Performance monitoring

Slutsats

Viktigaste punkterna:

  1. Använd substantiv i URLs
  2. Rätt HTTP methods och status codes
  3. Konsistent naming och structure
  4. Robust error handling
  5. Säkerhet först (HTTPS, validation, rate limiting)
  6. Dokumentation
  7. Monitoring och logging

Med dessa best practices bygger du APIs som är:

  • Lätta att använda
  • Säkra
  • Skalbara
  • Underhållbara
  • Väldokumenterade

Lycka till med din API-design! 🚀