Skip to main content
Backend Development

RESTful API Design Best Practices | Maintainable API Development | GraphQL vs REST | Bridge Software Solutions

BS
Backend Development Division Lead Architect
Bridge Software Solutions
14 min

RESTful API Design Best Practices 2026

Why Excellent API Design Matters

APIs (Application Programming Interfaces) are among the most critical elements in modern software development. Mobile apps, web applications, IoT devices, third-party integrations—all communicate through APIs.

However, poor API design causes serious problems:

  • Increased maintenance cost: Large-scale modifications needed for every change
  • Performance issues: Excessive data fetching, N+1 problems
  • Security risks: Improper authentication/authorization
  • Reduced development efficiency: Lack of documentation, inconsistent design

At Bridge Software Solutions, through over 500 API development projects, we've established best practices for scalable, maintainable API design.

📊 Benefits of Excellent API Design

  • Development Efficiency: 2.5x improvement average
  • Maintenance Cost: 58% reduction average
  • Bug Occurrence: 72% reduction average
  • API Response Time: 65% improvement average
  • External Integration Development: 3x faster average

1. RESTful API Design Fundamentals

Resource-Oriented URL Design

API URLs express resources (nouns), while HTTP methods express actions (verbs).

✅ Good Examples

GET    /api/users              # Get user list
GET    /api/users/123          # Get specific user
POST   /api/users              # Create user
PUT    /api/users/123          # Update user (full)
PATCH  /api/users/123          # Update user (partial)
DELETE /api/users/123          # Delete user

GET    /api/users/123/posts    # Get user's posts
GET    /api/posts/456/comments # Get post's comments

❌ Bad Examples

❌ GET  /api/getUsers           # Using verbs
❌ POST /api/createUser         # Using verbs
❌ GET  /api/user/delete/123    # DELETE with GET
❌ POST /api/updateUser         # UPDATE with POST

Proper HTTP Method Usage

MethodPurposeIdempotentSafe
GETRetrieve resource
POSTCreate resource
PUTUpdate entire resource
PATCHUpdate partial resource
DELETEDelete resource

Proper HTTP Status Code Usage

// Success responses
200 OK              // Success (typical GET, PUT, PATCH)
201 Created         // Resource creation success (POST)
204 No Content      // Success (no response body)

// Client errors
400 Bad Request     // Invalid request
401 Unauthorized    // Authentication required
403 Forbidden       // Insufficient permissions
404 Not Found       // Resource doesn't exist
409 Conflict        // Resource conflict
422 Unprocessable   // Validation error
429 Too Many Requests // Rate limit exceeded

// Server errors
500 Internal Server Error // Internal server error
502 Bad Gateway          // Gateway error
503 Service Unavailable  // Service unavailable

2. API Response Design

Unified Response Structure

// Success response
interface SuccessResponse<T> {
  success: true;
  data: T;
  meta?: {
    pagination?: {
      total: number;
      page: number;
      perPage: number;
      totalPages: number;
    };
    timestamp: string;
  };
}

// Error response
interface ErrorResponse {
  success: false;
  error: {
    code: string;
    message: string;
    details?: Array<{
      field: string;
      message: string;
    }>;
  };
  meta: {
    timestamp: string;
    requestId: string;
  };
}

// Usage example
const successResponse: SuccessResponse<User[]> = {
  success: true,
  data: [
    { id: 1, name: 'Taro Yamada', email: 'taro@example.com' },
    { id: 2, name: 'Hanako Suzuki', email: 'hanako@example.com' }
  ],
  meta: {
    pagination: {
      total: 150,
      page: 1,
      perPage: 20,
      totalPages: 8
    },
    timestamp: '2026-01-20T10:30:00Z'
  }
};

const errorResponse: ErrorResponse = {
  success: false,
  error: {
    code: 'VALIDATION_ERROR',
    message: 'Validation failed',
    details: [
      { field: 'email', message: 'Invalid email format' },
      { field: 'password', message: 'Password must be at least 8 characters' }
    ]
  },
  meta: {
    timestamp: '2026-01-20T10:30:00Z',
    requestId: 'req_abc123def456'
  }
};

Pagination

// Cursor-based pagination (recommended)
GET /api/posts?cursor=abc123&limit=20

{
  "success": true,
  "data": [...],
  "meta": {
    "pagination": {
      "nextCursor": "def456",
      "prevCursor": "xyz789",
      "hasMore": true
    }
  }
}

// Offset-based pagination
GET /api/posts?page=2&per_page=20

{
  "success": true,
  "data": [...],
  "meta": {
    "pagination": {
      "total": 1500,
      "page": 2,
      "perPage": 20,
      "totalPages": 75
    }
  }
}

Field Selection and Data Optimization

// Get only necessary fields
GET /api/users?fields=id,name,email

// Response
{
  "success": true,
  "data": [
    { "id": 1, "name": "Taro", "email": "taro@example.com" },
    { "id": 2, "name": "Hanako", "email": "hanako@example.com" }
  ]
}

// Expand related data
GET /api/posts?expand=author,comments

// Response
{
  "success": true,
  "data": [
    {
      "id": 1,
      "title": "My Post",
      "author": {
        "id": 5,
        "name": "Taro"
      },
      "comments": [
        { "id": 10, "text": "Great post!" }
      ]
    }
  ]
}

3. Authentication and Authorization

Authentication with JWT (JSON Web Token)

// Login endpoint
POST /api/auth/login
Content-Type: application/json

{
  "email": "user@example.com",
  "password": "securePassword123"
}

// Response
{
  "success": true,
  "data": {
    "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "expiresIn": 3600,
    "tokenType": "Bearer"
  }
}

// Call API requiring authentication
GET /api/users/me
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

JWT Implementation Example (Node.js + Express)

import jwt from 'jsonwebtoken';
import bcrypt from 'bcrypt';

// Login process
app.post('/api/auth/login', async (req, res) => {
  const { email, password } = req.body;
  
  // Find user
  const user = await User.findOne({ email });
  if (!user) {
    return res.status(401).json({
      success: false,
      error: {
        code: 'INVALID_CREDENTIALS',
        message: 'Invalid email or password'
      }
    });
  }
  
  // Verify password
  const isValid = await bcrypt.compare(password, user.passwordHash);
  if (!isValid) {
    return res.status(401).json({
      success: false,
      error: {
        code: 'INVALID_CREDENTIALS',
        message: 'Invalid email or password'
      }
    });
  }
  
  // Issue JWT
  const accessToken = jwt.sign(
    { userId: user.id, email: user.email, role: user.role },
    process.env.JWT_SECRET,
    { expiresIn: '1h' }
  );
  
  const refreshToken = jwt.sign(
    { userId: user.id },
    process.env.JWT_REFRESH_SECRET,
    { expiresIn: '7d' }
  );
  
  res.json({
    success: true,
    data: { accessToken, refreshToken, expiresIn: 3600 }
  });
});

// Authentication middleware
const authenticate = (req, res, next) => {
  const authHeader = req.headers.authorization;
  
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({
      success: false,
      error: {
        code: 'MISSING_TOKEN',
        message: 'Authentication token is required'
      }
    });
  }
  
  const token = authHeader.substring(7);
  
  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch (error) {
    return res.status(401).json({
      success: false,
      error: {
        code: 'INVALID_TOKEN',
        message: 'Invalid or expired token'
      }
    });
  }
};

// Role-based authorization middleware
const authorize = (...roles) => {
  return (req, res, next) => {
    if (!roles.includes(req.user.role)) {
      return res.status(403).json({
        success: false,
        error: {
          code: 'INSUFFICIENT_PERMISSIONS',
          message: 'You do not have permission to access this resource'
        }
      });
    }
    next();
  };
};

// Usage example
app.get('/api/users', authenticate, async (req, res) => {
  // All authenticated users can access
});

app.delete('/api/users/:id', authenticate, authorize('admin'), async (req, res) => {
  // Only admins can access
});

4. API Versioning

URL Path Versioning (Recommended)

// v1 API
GET /api/v1/users

// v2 API (with breaking changes)
GET /api/v2/users

// Implementation example
app.get('/api/v1/users', handleV1Users);
app.get('/api/v2/users', handleV2Users);

Header-Based Versioning

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

// Or
GET /api/users
API-Version: 1

Version Migration Strategy

// Include deprecation warning
{
  "success": true,
  "data": [...],
  "meta": {
    "deprecated": {
      "message": "This endpoint is deprecated and will be removed on 2026-06-01",
      "newEndpoint": "/api/v2/users",
      "documentationUrl": "https://docs.example.com/migration/v1-to-v2"
    }
  }
}

5. Rate Limiting and Throttling

Rate Limiting Implementation

import rateLimit from 'express-rate-limit';

// Basic rate limiting
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // Max 100 requests
  message: {
    success: false,
    error: {
      code: 'RATE_LIMIT_EXCEEDED',
      message: 'Too many requests, please try again later'
    }
  },
  standardHeaders: true,
  legacyHeaders: false,
});

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

// Endpoint-specific rate limiting
const strictLimiter = rateLimit({
  windowMs: 60 * 1000,
  max: 5, // 5 requests per minute
});

app.post('/api/auth/login', strictLimiter, handleLogin);

// User-specific rate limiting
const userLimiter = rateLimit({
  windowMs: 60 * 1000,
  max: async (req) => {
    // Adjust limit based on user plan
    const user = req.user;
    if (user.plan === 'premium') return 1000;
    if (user.plan === 'standard') return 100;
    return 10;
  },
  keyGenerator: (req) => req.user.id,
});

Rate Limit Headers

HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1642771200

6. Error Handling

Comprehensive Error Response

// Error code definitions
enum ErrorCode {
  // Authentication/Authorization
  UNAUTHORIZED = 'UNAUTHORIZED',
  FORBIDDEN = 'FORBIDDEN',
  INVALID_TOKEN = 'INVALID_TOKEN',
  
  // Validation
  VALIDATION_ERROR = 'VALIDATION_ERROR',
  INVALID_INPUT = 'INVALID_INPUT',
  
  // Resources
  NOT_FOUND = 'NOT_FOUND',
  ALREADY_EXISTS = 'ALREADY_EXISTS',
  CONFLICT = 'CONFLICT',
  
  // Rate limiting
  RATE_LIMIT_EXCEEDED = 'RATE_LIMIT_EXCEEDED',
  
  // Server
  INTERNAL_ERROR = 'INTERNAL_ERROR',
  SERVICE_UNAVAILABLE = 'SERVICE_UNAVAILABLE',
}

// Error handler middleware
app.use((err, req, res, next) => {
  // Logging
  console.error('Error:', {
    message: err.message,
    stack: err.stack,
    requestId: req.id,
    userId: req.user?.id,
  });
  
  // Validation error
  if (err.name === 'ValidationError') {
    return res.status(422).json({
      success: false,
      error: {
        code: ErrorCode.VALIDATION_ERROR,
        message: 'Validation failed',
        details: Object.keys(err.errors).map(field => ({
          field,
          message: err.errors[field].message
        }))
      },
      meta: {
        timestamp: new Date().toISOString(),
        requestId: req.id
      }
    });
  }
  
  // Database error
  if (err.code === 11000) {
    return res.status(409).json({
      success: false,
      error: {
        code: ErrorCode.ALREADY_EXISTS,
        message: 'Resource already exists'
      },
      meta: {
        timestamp: new Date().toISOString(),
        requestId: req.id
      }
    });
  }
  
  // Default error
  res.status(500).json({
    success: false,
    error: {
      code: ErrorCode.INTERNAL_ERROR,
      message: 'An unexpected error occurred'
    },
    meta: {
      timestamp: new Date().toISOString(),
      requestId: req.id
    }
  });
});

7. API Documentation (OpenAPI/Swagger)

OpenAPI 3.0 Specification

openapi: 3.0.0
info:
  title: Bridge Software Solutions API
  version: 1.0.0
  description: Enterprise-grade API for business management
  contact:
    name: API Support
    email: api-support@bridgesoftware.jp

servers:
  - url: https://api.bridgesoftware.jp/v1
    description: Production server
  - url: https://staging-api.bridgesoftware.jp/v1
    description: Staging server

paths:
  /users:
    get:
      summary: Get all users
      tags:
        - Users
      security:
        - bearerAuth: []
      parameters:
        - in: query
          name: page
          schema:
            type: integer
            default: 1
          description: Page number
        - in: query
          name: per_page
          schema:
            type: integer
            default: 20
          description: Items per page
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/User'
        '401':
          $ref: '#/components/responses/Unauthorized'

components:
  schemas:
    User:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        email:
          type: string
          format: email
        role:
          type: string
          enum: [user, admin]
  
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
  
  responses:
    Unauthorized:
      description: Unauthorized
      content:
        application/json:
          schema:
            type: object
            properties:
              success:
                type: boolean
                example: false
              error:
                type: object
                properties:
                  code:
                    type: string
                  message:
                    type: string

8. GraphQL vs REST: When to Use Which

REST API is Suitable When:

  • ✅ Simple CRUD operations
  • ✅ Maximize caching utilization
  • ✅ File upload/download
  • ✅ Use standard HTTP status codes
  • ✅ Team familiar with REST

GraphQL is Suitable When:

  • ✅ Complex data fetching requirements
  • ✅ Avoid over-fetching/under-fetching
  • ✅ Real-time updates (Subscriptions)
  • ✅ Type safety is critical
  • ✅ Mobile apps (reduce data transfer)

Summary | Excellent API Design is Long-Term Investment

Excellent API design directly leads to improved development efficiency, reduced maintenance costs, and system scalability. Following proper design principles from the initial stage maximizes long-term ROI.

At Bridge Software Solutions, based on over 500 API development implementations, we propose optimal API designs for your business. Whether RESTful API or GraphQL is suitable, we also offer free consulting.

🚀 Next Steps

  1. Get free API design review
  2. OpenAPI specification creation support
  3. Technical support contract for continuous improvement

Based in Tokyo, serving nationwide. Contact us today.

BS
Backend Development Division Lead Architect
Bridge Software Solutions | Technology leader supporting digital transformation for 500+ companies across Japan from our Tokyo headquarters.
Learn more about us

Have a Technical Challenge?

Bridge Software Solutions' expert team proposes the optimal solution for your business.