For over a decade, developers learning OpenAPI have started with the same example: the Swagger Petstore. It's been the go-to tutorial for understanding API specifications. But here's the problem—this widely-used example teaches anti-patterns that developers carry into production systems.
The old Petstore doesn't follow basic RESTful design principles. If you learned API design from it, you might be building APIs the wrong way.
The Three Critical Violations
1. Inconsistent Resource Naming
The old Petstore mixes singular and plural resource names:
GET /pet/{id} ← Singular
GET /store/inventory ← Plural
POST /user ← Singular
This inconsistency creates confusion. Should you use /pet/123 or /pets/123? The answer: always use plural for collections.
Here's why plural wins:
Consistency across operations. When you use /pets, it works for both collections and individual resources: - GET /pets returns all pets - GET /pets/123 returns one pet - POST /pets creates a pet - PUT /pets/123 updates a pet
Clearer semantics. /pets/123 reads as "pet 123 from the pets collection." /pet/123 reads awkwardly.
Industry standard. GitHub uses /repos, Stripe uses /customers, Twitter uses /tweets. The pattern is established.
The Modern PetStore API fixes this:
GET /pets/{id}
GET /orders/{id}
GET /users/{id}
Every resource uses plural naming. No exceptions.
2. Action Verbs in URLs
The old Petstore includes endpoints like:
GET /pet/findByStatus?status=available
GET /pet/findByTags?tags=tag1,tag2
This violates a core REST principle: URLs should represent resources (nouns), not actions (verbs).
The HTTP method already tells you the action: - GET = retrieve - POST = create - PUT = update - DELETE = remove
Adding "find" to the URL is redundant. Worse, it creates inconsistency. Why is it /pet/findByStatus but not /pet/getById?
The correct approach uses query parameters:
GET /pets?status=available
GET /pets?tags=tag1,tag2
For complex searches that don't fit query parameters, use the QUERY method:
QUERY /pets/search
Content-Type: application/json
{
"filters": {
"status": "AVAILABLE",
"breed": "Golden Retriever",
"age": {"min": 1, "max": 5}
}
}
The QUERY method (RFC 9535) is designed for complex queries that need a request body. It's semantically correct and avoids URL bloat.
3. Wrong HTTP Status Codes
The old Petstore returns incorrect status codes:
POST /pet
Response: 200 OK
DELETE /pet/{id}
Response: 200 OK with body
Both are wrong.
Creating resources should return 201 Created, not 200 OK. The 201 status tells clients a new resource exists and includes its location in the Location header:
POST /pets
Response: 201 Created
Location: /pets/019b4132-70aa-764f-b315-e2803d882a24
{
"id": "019b4132-70aa-764f-b315-e2803d882a24",
"name": "Max",
"status": "AVAILABLE"
}
Deleting resources should return 204 No Content, not 200 OK. The 204 status indicates success without a response body. Returning 200 with a body wastes bandwidth and creates confusion—what should the body contain?
DELETE /pets/019b4132-70aa-764f-b315-e2803d882a24
Response: 204 No Content
The Modern PetStore API uses correct status codes throughout: - 200 OK for successful GET/PUT with body - 201 Created for successful POST - 204 No Content for successful DELETE - 400 Bad Request for client errors - 422 Unprocessable Entity for validation errors - 500 Internal Server Error for server failures
What Else Is Missing?
Beyond the three critical violations, the old Petstore lacks features you need in production:
No Standard Error Format
The old Petstore returns generic error messages with no structure. Modern APIs use RFC 9457 Problem Details:
{
"type": "https://petstoreapi.com/errors/validation-error",
"title": "Validation Error",
"status": 422,
"detail": "The request body contains validation errors",
"instance": "/v1/pets",
"errors": [
{
"field": "name",
"message": "Name is required"
}
]
}
This format is standardized, machine-readable, and includes enough context for debugging.
No Pagination
The old Petstore returns all results in one response. This doesn't scale. What happens when you have 10,000 pets?
Modern APIs use cursor-based pagination:
GET /pets?limit=20&cursor=eyJpZCI6IjAxOWI0MTMyIn0
Response:
{
"data": [...],
"pagination": {
"nextCursor": "eyJpZCI6IjAxOWI0MTUzIn0",
"hasMore": true
}
}
Cursor pagination scales to millions of records without performance degradation.
No Rate Limiting
Production APIs need rate limiting to prevent abuse. The Modern PetStore API includes standard rate limit headers:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1678886400
Clients can adjust their behavior before hitting limits.
No Versioning Strategy
The old Petstore doesn't address versioning. How do you introduce breaking changes?
Modern APIs version through the URL:
GET /v1/pets/{id}
GET /v2/pets/{id}
Or through headers:
GET /pets/{id}
Accept: application/vnd.petstore.v2+json
Both approaches work. Pick one and stick with it.
No Security Model
The old Petstore has minimal security examples. Modern APIs need:
OAuth 2.0 with scopes:
security:
- oauth2:
- read:pets
- write:pets
API key authentication:
Authorization: Bearer sk_live_abc123...
Rate limiting per user:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
Security isn't optional in production.
How Modern PetStore API Fixes Everything
The Modern PetStore API was built to demonstrate correct API design:
RESTful URLs
GET /pets ← List all pets
POST /pets ← Create a pet
GET /pets/{id} ← Get one pet
PUT /pets/{id} ← Update a pet
DELETE /pets/{id} ← Delete a pet
GET /pets?status=AVAILABLE ← Filter pets
Every URL follows REST principles. No action verbs. Consistent plural naming.
Correct Status Codes
- 200 OK for successful reads and updates
- 201 Created for successful creates
- 204 No Content for successful deletes
- 400 Bad Request for malformed requests
- 401 Unauthorized for missing auth
- 403 Forbidden for insufficient permissions
- 404 Not Found for missing resources
- 422 Unprocessable Entity for validation errors
- 429 Too Many Requests for rate limiting
- 500 Internal Server Error for server failures
Standard Error Format (RFC 9457)
{
"type": "https://petstoreapi.com/errors/not-found",
"title": "Resource Not Found",
"status": 404,
"detail": "Pet with ID 019b4132-70aa-764f-b315-e2803d882a24 not found",
"instance": "/v1/pets/019b4132-70aa-764f-b315-e2803d882a24"
}
Cursor-Based Pagination
{
"data": [...],
"pagination": {
"nextCursor": "eyJpZCI6IjAxOWI0MTUzIn0",
"prevCursor": "eyJpZCI6IjAxOWI0MTMyIn0",
"hasMore": true
}
}
OAuth 2.0 Security
components:
securitySchemes:
oauth2:
type: oauth2
flows:
authorizationCode:
authorizationUrl: https://auth.petstoreapi.com/oauth/authorize
tokenUrl: https://auth.petstoreapi.com/oauth/token
scopes:
read:pets: View pet information
write:pets: Manage pets
Rate Limiting
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1678886400
Retry-After: 60
What You Should Do
If you're building a new API:
- Use plural resource names (
/pets, not/pet) - Keep actions in HTTP methods (GET, POST, PUT, DELETE)
- Return correct status codes (201 for creates, 204 for deletes)
- Implement RFC 9457 error format
- Add cursor-based pagination
- Include rate limiting headers
- Version your API (URL or header-based)
- Use OAuth 2.0 or API keys for authentication
If you're maintaining an existing API:
- Audit your endpoints for RESTful violations
- Document breaking changes in your changelog
- Version new endpoints that fix design issues
- Deprecate old endpoints with sunset headers
- Migrate clients gradually to new patterns
Don't break existing clients, but stop propagating bad patterns.
The Bigger Picture
The old Swagger Petstore served its purpose as a simple teaching tool. But "simple" became "simplistic." It taught a generation of developers that REST is just HTTP + JSON.
REST is more than that. It's a set of architectural constraints that make APIs scalable, maintainable, and predictable.
The Modern PetStore API shows what's possible when you apply those constraints correctly. It's not just an example—it's a reference implementation you can learn from and build upon.
Your API is your product's interface to the world. Design it well.
Key Takeaways
- The old Swagger Petstore violates basic RESTful principles
- Use plural resource names consistently (
/pets, not/pet) - Keep action verbs out of URLs—use HTTP methods instead
- Return correct status codes (201 for creates, 204 for deletes)
- Implement standard error formats (RFC 9457)
- Add pagination, rate limiting, and proper security
- The Modern PetStore API demonstrates correct patterns
from Anakin Blog http://anakin.ai/blog/swagger-petstore-teaches-bad-design/
via IFTTT
No comments:
Post a Comment