Skip to content

API Design Principles

Designing a well-structured API is crucial for creating maintainable, scalable, and developer-friendly backend services. This guide covers essential principles and best practices for designing RESTful APIs with Express.js.

When creating endpoints with multiple words, use kebab-case (lowercase with hyphens):

DO use kebab-case for multi-word resources:

GET /user-profiles
GET /product-categories
GET /order-items

DON’T use camelCase or snake_case in URLs:

GET /userProfiles // Avoid camelCase
GET /product_categories // Avoid snake_case

Structure your endpoints around resources (nouns) rather than actions (verbs):

DO use resource-based naming:

// Resource collections
GET /users
POST /users
// Specific resource
GET /users/:id
PUT /users/:id
DELETE /users/:id

DON’T use action-based naming:

// Avoid these patterns
GET /getUsers
POST /createUser
PUT /updateUser/:id

While most endpoints should use nouns, there are exceptions for operation-based actions that don’t fit the standard CRUD model:

// Operation-based endpoints should use verbs
POST /users/:id/reset-password
POST /emails/send
GET /export-data

These are appropriate for actions that:

  • Trigger a process or calculation
  • Don’t directly map to a single resource
  • Represent a state transition

Express nested resources through URL paths:

// Get all posts by a specific user
GET /users/:userId/posts
// Get a specific post from a specific user
GET /users/:userId/posts/:postId

Use consistent pluralization for resource collections:

DO:

GET /users // List of users
GET /users/:id // Specific user
GET /categories // List of categories

DON’T mix singular and plural:

GET /user // Inconsistent with other endpoints
GET /categories/:categoryId/post // Should be /posts

Use appropriate HTTP methods for different operations:

MethodPurposeExample
GETRead/retrieve dataGET /products - Retrieve all products
POSTCreate new resourcesPOST /products - Create a new product
PUTUpdate existing resources (complete replacement)PUT /products/:id - Replace entire product
PATCHPartial updatePATCH /products/:id - Update specific fields
DELETERemove resourcesDELETE /products/:id - Delete a product

When defining query parameters and dynamic URL parameters, use camelCase for consistency:

// Correct query parameter naming with camelCase
GET /products?minPrice=10&maxPrice=100
GET /users?firstName=John&lastName=Doe
GET /orders?createdAfter=2023-01-01
// For dynamic URL parameters, also use camelCase
GET /users/:userId/posts/:postId
GET /orders/:orderId/items/:itemId

This maintains consistency with JavaScript naming conventions and makes frontend development smoother when parsing API responses and constructing requests.

Allow clients to filter collections:

GET /users?role=admin
GET /products?category=electronics&inStock=true

Enable sorting by specific fields:

GET /users?sort=lastName
GET /products?sort=-price // Descending order with - prefix

Implement pagination to handle large result sets:

GET /products?page=2&limit=10

Consistently use camelCase for all JSON properties in your responses:

// Correct JSON property naming
{
"userId": 123,
"firstName": "John",
"lastName": "Doe",
"orderItems": [
{ "productId": 456, "quantityOrdered": 2 }
],
}

Include the Total Number of Resources in Your Response

Section titled “Include the Total Number of Resources in Your Response”

When returning collections, especially paginated ones, always include metadata about the total count:

// Response with metadata including total count
{
"statusCode": 200,
"data": [
// Array of resources
],
"meta": {
"totalCount": 1357,
"page": 2,
"limit": 25,
"totalPages": 55
}
}

This helps clients to:

  • Build proper pagination controls
  • Show accurate counts to users
  • Determine when they’ve reached the end of the collection
  1. Be consistent with property naming
    • Stick to camelCase throughout your entire API
  2. Use string format for complex identifiers
    • UUIDs, large numbers should be strings to avoid precision issues
  3. Avoid nested objects deeper than 3 levels
    • Deeply nested objects become hard to parse and understand

Define a consistent response structure for all endpoints:

// Success response
{
"statusCode": 200,
"data": {
// Resource data here
},
"message": "Users retrieved successfully",
"meta": {
// Pagination, count, etc. (optional)
"totalCount": 150,
"page": 2,
"limit": 20
}
}
// Error response
{
"statusCode": 400,
"message": "Invalid user data provided",
"path": "/api/v1/users",
"timestamp": "2023-07-25T15:30:45.123Z"
}

Use appropriate HTTP status codes to indicate the outcome of requests:

Status CodeDescriptionWhen to Use
200 OKRequest succeededSuccessful GET, PUT, PATCH, or DELETE
201 CreatedResource createdSuccessful POST that created a resource
204 No ContentSuccess with no content to returnSuccessful DELETE or PUT with no response body
400 Bad RequestInvalid requestMalformed request syntax, invalid parameters
401 UnauthorizedAuthentication requiredMissing or invalid authentication
403 ForbiddenAuthenticated but not authorizedUser lacks permission
404 Not FoundResource not foundRequested resource doesn’t exist
409 ConflictRequest conflicts with current stateDuplicate resource, version conflicts, concurrent updates
429 Too Many RequestsRate limit exceededClient has sent too many requests in a given time period
500 Internal Server ErrorServer errorException occurred on the server

Versioning allows you to evolve your API without breaking existing clients.

Include the version in the base path:

/api/v1/users
/api/v2/users

Inconsistent naming patterns

  • Mixing plural and singular resources
  • Using different naming conventions across endpoints

Exposing database details

  • Returning internal error details to clients
  • Exposing database IDs or structure directly

Not using HTTP methods appropriately

  • Using GET requests for data modification
  • Using POST for everything

Returning inappropriate status codes

  • Returning 200 for errors
  • Using 500 for client errors

No rate limiting

  • Leaving your API open to abuse

Inconsistent data format

  • Mixing date formats (use ISO 8601: YYYY-MM-DDTHH:MM:SSZ)
  • Inconsistent number formatting

Designing a well-structured API is essential for building scalable, maintainable applications. By following these principles, you can create APIs that are intuitive for developers to use, perform well, and can evolve over time.

When designing your API, remember these key points:

  1. Use resource-based URL naming with appropriate HTTP methods
  2. Implement consistent response structures and status codes
  3. Version your API to allow for evolution
  4. Include proper filtering, sorting, and pagination
  5. Implement security best practices from the beginning