iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🌐

Designing REST APIs Without Regrets

に公開

To Avoid Regret in REST API Design

When you start backend development, you immediately face the challenge of API design. It is not uncommon for an API built with the mindset of "as long as it works for now" to cause major problems later. In this article, I will introduce three important principles of REST API design along with practical design examples.

Common API Design Failures

Here are some problems frequently seen in development environments.

❌ Bad Design
POST /createUser
GET  /getUserList
POST /deleteUserById?id=123
GET  /api/user_posts/getUserPostsByID

What is wrong with this design?

  • Verbs are included in the URL (createUser, getUserList)
  • Incorrect use of HTTP methods (using POST for deletion)
  • Inconsistent naming conventions (snake_case, camelCase)
  • Improper use of query parameters

These issues can be avoided by understanding the basic principles of REST API design.

Three Principles of REST API Design

1. Think Resource-Oriented

The most important concept in REST is the "resource." A URL should represent "what you are operating on" rather than "what you are doing."

Principles:

  • Use nouns in URLs (do not use verbs)
  • Use plural forms (to represent collections)
  • Clarify parent-child relationships through a hierarchical structure
✅ Good Design
GET    /users              # List users
POST   /users              # Create user
GET    /users/123          # Specific user
PUT    /users/123          # Update user
DELETE /users/123          # Delete user
GET    /users/123/posts    # List user's posts

2. Use HTTP Methods Correctly

Each HTTP method has a clear role.

Method Purpose Idempotency
GET Resource retrieval
POST Resource creation
PUT Full resource update
PATCH Partial resource update
DELETE Resource deletion

Idempotency refers to the property where "performing the same request multiple times yields the same result."

Implementation Example:

import { Router } from 'express'
import { PrismaClient } from '@prisma/client'

const router = Router()
const prisma = new PrismaClient()

// GET - Resource retrieval
router.get('/users/:id', async (req, res) => {
  const user = await prisma.user.findUnique({
    where: { id: req.params.id },
  })

  if (!user) {
    return res.status(404).json({
      error: { code: 'USER_NOT_FOUND', message: 'User not found' }
    })
  }

  res.json({ success: true, data: user })
})

// POST - Resource creation
router.post('/users', async (req, res) => {
  const { email, name } = req.body

  const user = await prisma.user.create({
    data: { email, name },
  })

  res.status(201)
    .setHeader('Location', `/users/${user.id}`)
    .json({ success: true, data: user })
})

// PATCH - Partial update
router.patch('/users/:id', async (req, res) => {
  const updates = req.body

  const user = await prisma.user.update({
    where: { id: req.params.id },
    data: updates,
  })

  res.json({ success: true, data: user })
})

// DELETE - Resource deletion
router.delete('/users/:id', async (req, res) => {
  await prisma.user.delete({
    where: { id: req.params.id },
  })

  res.status(204).send() // No Content
})

3. Choosing Appropriate Status Codes

HTTP status codes are an important means of communicating processing results to the client.

Success Responses (2xx):

  • 200 OK: Success for GET/PUT/PATCH
  • 201 Created: Resource successfully created via POST
  • 204 No Content: Success for DELETE (no response body)

Client Errors (4xx):

  • 400 Bad Request: Validation error
  • 401 Unauthorized: Authentication error
  • 403 Forbidden: Insufficient permissions
  • 404 Not Found: Resource does not exist
  • 409 Conflict: Resource conflict (e.g., duplicate email address)

Server Errors (5xx):

  • 500 Internal Server Error: Unexpected error
  • 503 Service Unavailable: Service temporarily unavailable
// 404 Not Found
app.get('/users/:id', async (req, res) => {
  const user = await findUser(req.params.id)

  if (!user) {
    return res.status(404).json({
      error: {
        code: 'USER_NOT_FOUND',
        message: 'User not found'
      }
    })
  }

  res.json({ data: user })
})

// 409 Conflict
app.post('/users', async (req, res) => {
  const existing = await findUserByEmail(req.body.email)

  if (existing) {
    return res.status(409).json({
      error: {
        code: 'EMAIL_ALREADY_EXISTS',
        message: 'Email already exists'
      }
    })
  }

  const user = await createUser(req.body)
  res.status(201).json({ data: user })
})

Practical Design Example: Task Management API

Based on the principles discussed so far, let's design a simple task management API.

// GET /tasks - Task list (with pagination)
router.get('/tasks', async (req, res) => {
  const page = parseInt(req.query.page as string) || 1
  const limit = parseInt(req.query.limit as string) || 10
  const skip = (page - 1) * limit

  const [tasks, total] = await Promise.all([
    prisma.task.findMany({
      skip,
      take: limit,
      orderBy: { createdAt: 'desc' },
    }),
    prisma.task.count(),
  ])

  res.json({
    success: true,
    data: tasks,
    meta: {
      page,
      limit,
      total,
      totalPages: Math.ceil(total / limit),
      hasNextPage: page < Math.ceil(total / limit),
    },
  })
})

// POST /tasks - Create task
router.post('/tasks', async (req, res) => {
  const { title, description } = req.body

  // Validation
  if (!title || title.trim().length === 0) {
    return res.status(400).json({
      error: {
        code: 'VALIDATION_ERROR',
        message: 'Title is required',
      },
    })
  }

  const task = await prisma.task.create({
    data: {
      title,
      description,
      status: 'todo',
    },
  })

  res.status(201)
    .setHeader('Location', `/tasks/${task.id}`)
    .json({ success: true, data: task })
})

// PATCH /tasks/:id - Update task
router.patch('/tasks/:id', async (req, res) => {
  try {
    const task = await prisma.task.update({
      where: { id: req.params.id },
      data: req.body,
    })

    res.json({ success: true, data: task })
  } catch (error) {
    res.status(404).json({
      error: {
        code: 'TASK_NOT_FOUND',
        message: 'Task not found',
      },
    })
  }
})

Key points of this implementation:

  1. Pagination: Efficiently retrieve large amounts of data.
  2. Unified response format: Maintain consistency using { success, data, meta }.
  3. Appropriate status codes: Use 201 (Created), 204 (Deleted), 404 (Not Found) correctly.
  4. Error handling: Provide easy-to-understand error codes and messages.

Summary

Let's review the three principles of REST API design.

  1. Resource-Oriented: URLs should be nouns and represent resources.
  2. Correct Use of HTTP Methods: Use GET/POST/PUT/PATCH/DELETE appropriately.
  3. Appropriate Selection of Status Codes: Clearly communicate processing results using 2xx/4xx/5xx.

By simply following these principles, you can design APIs that are highly maintainable and intuitive to use.

🌐 Learn More About Robust API Design

Professional API Development Through Books

While this article explained the basics of REST APIs, more advanced implementations are required in actual production environments.

Complete Guide to Error Handling

  • Error response design compliant with RFC 7807
  • Retry strategies and circuit breaker patterns
  • Timeout control and fallback processing

API Versioning Strategies

  • Comparison of URI, header, and query parameter methods
  • Design patterns for maintaining backward compatibility
  • Implementation of gradual deprecation

Implementation of Authentication and Authorization

  • Selection criteria for JWT vs. Session vs. OAuth 2.0
  • RBAC/ABAC (Role/Attribute-Based Access Control)
  • Security best practices

Full E-commerce Backend Implementation

  • Payment API integration (Stripe, PayPal)
  • Design of inventory management systems
  • Implementation of order processing flows

Optimization Based on Real-World Data

  • API response time from 95ms to 28ms (-70%)
  • Error rate from 4.2% to 0.3% (-93%)
  • A complete 520,000-character guide

📚 Backend Development Complete Guide 2026
👉 https://zenn.dev/gaku52/books/backend-development-complete-guide-2026

GitHubで編集を提案

Discussion