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
| Method | Purpose | Idempotent | Safe |
|---|---|---|---|
| GET | Retrieve resource | ✅ | ✅ |
| POST | Create resource | ❌ | ❌ |
| PUT | Update entire resource | ✅ | ❌ |
| PATCH | Update partial resource | ❌ | ❌ |
| DELETE | Delete 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
- Get free API design review
- OpenAPI specification creation support
- Technical support contract for continuous improvement
Based in Tokyo, serving nationwide. Contact us today.
Have a Technical Challenge?
Bridge Software Solutions' expert team proposes the optimal solution for your business.