Much of the past 6 years of my professional life has been spent designing, implementing and maintaining APIs. I’ve learned many things to avoid as well as a handful of good techniques. However, it wasn’t until 3 months ago that I was exposed to a practice I consider the most important thing you can do while building APIs. Every production-quality API I build going forward will be rigidly and explicitly specified.
What does it mean to specify an API? In the abstract, it mostly means writing things down as you make design decisions and doing so in a machine-parsable format. In practice, it means using either Open API (fka Swagger) or JSON Schema.
This post doesn’t advocate for a particular tool but rather the practice of specification itself.
Quick Example
Suppose your API exposes a /users
resource with the standard create
, index
and show
actions. Below is an example of what a specification for this
resource could look like using Open API.
definitions:
error:
type: object
required:
- code
- user_message
- developer_message
properties:
code:
type: integer
user_message:
type: string
developer_message:
type: string
user:
type: object
required:
- id
- name
properties:
id:
type: string
name:
type: string
age:
type: integer
paths:
/users:
get:
summary: Get a list of users
responses:
200:
description: Successful response
schema:
type: array
items: "#/definitions/user"
default:
description: Unsuccessful response
schema: "#/definitions/error"
post:
summary: Create a user
parameters:
- name: body
in: body
required: true
schema:
type: object
required:
- name
properties:
name:
type: string
age:
type: integer
responses:
201:
description: Successful response
schema: "#/definitions/user"
default:
description: Unsuccessful response
schema: "#/definitions/error"
/users/{id}:
get:
summary: Get a user
parameters:
- name: id
in: path
required: true
type: string
responses:
200:
description: Successful response
schema:
type: array
items: "#/definitions/user"
default:
description: Unsuccessful response
schema: "#/definitions/error"
The details of the specification aren’t terribly important but you should note a few things:
- Endpoints are defined via a combination of paths and HTTP verbs
- Responses are defined for a given endpoint based on status code
- Schemas can be provided for both requests and responses
- Schemas can be shared by giving them a name in the
definitions
object
Why Should I Do This?
The biggest benefit derived from specifying an API is actually writing down the functionality. I can’t count the number of times an idea seemed reasonable to me until I actually wrote out the definition for the endpoint and, upon seeing it, recognized all sorts of issues with the design. Think about this as TDD for API design. Write down the API you want before you build it.
After you’ve written down your APIs functionality, there are all sorts of cool things you can do with it. Two of the biggest problems this solves are request / response validation and documentation generation.
We can use the specification to build a validation middleware for our API’s requests and responses. Many people have already done this. Once we’re validating our requests and responses based on the specification and we build a solid set of API tests to exercise our API, we’re effectively testing the specification. This means the specification can be treated as a source of truth for our API’s functionality.
The specification is also extremely useful when producing documentation for our API. Because we’re running our test suite through the specification, we have an accurate, up-to-date data source from which to generate an API reference.
Last, but not least, specifying an API leads to much greater consistency. A
developer adding a new endpoint will automatically reuse the same error response
that other endpoints are using. A developer adding a new endpoint that exposes a
user
will use the existing user
definition. This leads to a consistent
representation of a given resource across our entire API. It’s actually harder
to do the wrong thing – you’d have to go out of your way to make your endpoint
return something non-standard. It’s hard to quantify how important this is, but
trust me, it’s very important.
Next time you build an API, take a bit of extra time up front to specify it. I promise you it’ll be worth the effort.