DEV Community

Alex Chen
Alex Chen

Posted on

JavaScript Console Tricks That Will Blow Your Mind

Designing REST APIs That Developers Actually Love Using

I've consumed hundreds of APIs. The good ones are a joy. The bad ones make you want to quit coding.

What Makes an API "Good"?

 Bad API:
GET /getUsers?userId=123
 { "data": { "user_info": { "name_str": "John" } }, "status": "success", "error": null }

 Good API:
GET /users/123
 { "id": 123, "name": "John Doe", "email": "john@example.com" }
Enter fullscreen mode Exit fullscreen mode

1. Resource Naming (The Foundation)

// ✅ Use nouns, not verbs
GET    /users          // List users
POST   /users          // Create user
GET    /users/123      // Get specific user
PUT    /users/123      // Update user (full)
PATCH  /users/123      // Partial update
DELETE /users/123      // Delete user

// ❌ Don't use verbs in URLs
GET /getAllUsers       // Wrong!
POST /createUser       // Wrong!
DELETE /deleteUser/123  // Wrong!

// ❌ Don't use URL query params for actions
POST /users?action=create        // Wrong!
PUT  /users?id=123&action=update // Wrong!

// Plural vs singular: Use PLURAL for collections
GET /users           // Collection → plural
GET /users/123/items // Sub-collection of user 123's items → plural
Enter fullscreen mode Exit fullscreen mode

2. Proper HTTP Methods & Status Codes

// Method semantics matter!
app.get('/users', listUsers);     // Read — safe, idempotent
app.post('/users', createUser);   // Create — not idempotent
app.put('/users/:id', replaceUser); // Full replace — idempotent
app.patch('/users/:id', updateUser); // Partial update — not idempotent
app.delete('/users/:id', deleteUser); // Remove — idempotent

// Status codes — use them correctly!
const StatusCodes = {
  // Success (2xx)
  OK: 200,                    // Standard success response
  Created: 201,               // POST created a resource
  NoContent: 204,             // DELETE succeeded (no body needed)

  // Redirection (3xx)
  NotModified: 304,           // Conditional GET, resource unchanged

  // Client Errors (4xx)
  BadRequest: 400,            // Malformed syntax
  Unauthorized: 401,         // Not authenticated
  Forbidden: 403,            // Authenticated but no permission
  NotFound: 404,             // Resource doesn't exist
  MethodNotAllowed: 405,     // Wrong HTTP method for this endpoint
  Conflict: 409,             // State conflict (e.g., duplicate email)
  UnprocessableEntity: 422,  // Valid request but semantic errors
  TooManyRequests: 429,      // Rate limited

  // Server Errors (5xx)
  InternalServerError: 500,  // Something went wrong
  NotImplemented: 501,       // Feature not implemented yet
};
Enter fullscreen mode Exit fullscreen mode

3. Consistent Response Format

// Option A: Simple (for public/read-heavy APIs)
// GET /users/123
{
  "id": 123,
  "name": "Alice",
  "email": "alice@example.com",
  "role": "admin",
  "created_at": "2026-01-15T10:30:00Z",
  "_links": {
    "self": "/users/123",
    "posts": "/users/123/posts"
  }
}

// Option B: Enveloped (for APIs that need metadata)
// GET /users?page=1&per_page=20
{
  "data": [
    { "id": 1, "name": "Alice" },
    { "id": 2, "name": "Bob" }
  ],
  "meta": {
    "page": 1,
    "per_page": 20,
    "total": 150,
    "total_pages": 8
  }
}

// Error responses (ALWAYS consistent!)
// ❌ Inconsistent errors
{ "error": "not found" }              // Sometimes string
{ "message": "User doesn't exist" }   // Sometimes message
{ "errors": ["not found"] }           // Sometimes array

// ✅ Consistent error format
{
  "error": {
    "code": "USER_NOT_FOUND",
    "message": "User with ID 999 does not exist",
    "status": 404,
    "details": {
      "field": "id",
      "value": 999,
      "expected": "existing user ID"
    }
  },
  "request_id": "req_abc123"
}
Enter fullscreen mode Exit fullscreen mode

4. Pagination Done Right

// ❌ Bad pagination (no way to navigate!)
GET /users?offset=0&limit=20
 { data: [...] }

// ✅ Cursor-based (for infinite feeds, real-time data)
GET /users?cursor=eyJpZCI6MTAwfQ&limit=20
{
  "data": [...],
  "pagination": {
    "cursor": "eyJpZCI6MTIwfQ",  // Next page cursor
    "has_next": true,
    "has_prev": false
  }
}

// ✅ Offset-based (for admin panels, sortable tables)
GET /users?page=2&per_page=20&sort=name&order=asc
{
  "data": [...],
  "meta": {
    "page": 2,
    "per_page": 20,
    "total": 150,
    "total_pages": 8,
    "has_prev": true,
    "has_next": true
  },
  "links": {
    "first": "/users?page=1&per_page=20",
    "prev": "/users?page=1&per_page=20",
    "next": "/users?page=3&per_page=20",
    "last": "/users?page=8&per_page=20"
  }
}
Enter fullscreen mode Exit fullscreen mode

5. Filtering, Sorting & Field Selection

// Filtering
GET /posts?status=published&author_id=5&tag=javascript
GET /products?price_min=10&price_max=100&category=electronics
GET /users?role=admin&active=true

// Sorting
GET /users?sort=created_at&order=desc
GET /posts?sort=-published_at,title  // - prefix = descending
GET /products?sort=price&order=asc

// Field selection (reduce payload size!)
GET /users/123?fields=id,name,email
// Returns only requested fields instead of full object

// Search
GET /posts?q=react+hooks&search_fields=title,body
GET /users?search=john&search_fields=name,email
Enter fullscreen mode Exit fullscreen mode

6. Versioning

// Option A: URL path versioning (most common)
/api/v1/users
/api/v2/users  // Breaking changes go here

// Option B: Header versioning (cleaner URLs)
Accept: application/vnd.api.v1+json
GET /api/users

// My recommendation: Start with URL versioning.
// It's explicit and works everywhere.

// When to bump versions?
// v1 → v2: Breaking changes (removed fields, changed types, new required fields)
// Within same version: Additive changes are OK (new optional fields, new endpoints)
Enter fullscreen mode Exit fullscreen mode

7. Authentication & Security

// API Key (for server-to-server or simple cases)
GET /api/users
X-API-Key: ak_live_abc123

// Bearer Token (JWT/OAuth) — most common for user-facing APIs
GET /api/users
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

// Security headers (always include!)
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('X-XSS-Protection', '1; mode=block');
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
res.setHeader('Access-Control-Allow-Origin', 'https://your-app.com');

// Rate limiting headers
res.setHeader('X-RateLimit-Limit', '1000');
res.setHeader('X-RateLimit-Remaining', '999');
res.setHeader('X-RateLimit-Reset', '1715841200');

// Request ID for tracing
res.setHeader('X-Request-ID', generateRequestId());
Enter fullscreen mode Exit fullscreen mode

8. Documentation (Non-Negotiable!)

# OpenAPI/Swagger spec — the standard
openapi: 3.0.0
info:
  title: My API
  version: 1.0.0
  description: A well-designed REST API

paths:
  /users:
    get:
      summary: List all users
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: per_page
          in: query
          schema:
            type: integer
            maximum: 100
            default: 20
      responses:
        '200':
          description: List of users
          content:
            application/json:
              example:
                data:
                  - id: 1
                    name: Alice
                meta:
                  total: 150
                  page: 1
        '401':
          description: Unauthorized
        '429':
          description: Rate limited
Enter fullscreen mode Exit fullscreen mode

Quick Checklist

□ Nouns for resources, verbs for HTTP methods
□ Plural collection names
□ Correct status codes (2xx/4xx/5xx)
□ Consistent error format across ALL endpoints
□ Pagination on list endpoints
□ Filtering, sorting, field selection support
□ API versioning strategy
□ Auth headers documented
□ Rate limiting with informative headers
□ Request ID for debugging
□ OpenAPI/Swagger documentation
□ Example requests/responses for each endpoint
Enter fullscreen mode Exit fullscreen mode

What's the best/worst API you've used?

Follow @armorbreak for more backend content.

Top comments (0)