Table of Contents
This article will take you through how to design a web-based RESTful API with the OpenAPI Specification. Then, we’ll have the option to take the file specification and use it to continue to design and collaborate on our API.
Furthermore, this article is a sibling article of Best Practices and Principles for API Design. The two pair well together, and you’ll see the principles and best practices applied to a real API spec here.
Before jumping into the design process, I’ll discuss some other APIs and possible ways to help design them.
Types of APIs
Below is a list of API architectures, specifications, frameworks, and protocols. People often place them in different categories, and to avoid confusion, we’ll outline all the APIs as one section and make distinctions in the description.
As a general note, we are talking about web-APIs, although APIs are not only web-based. There are APIs for desktop applications or browser applications. Essentially, any application can have an interface for other applications.
RESTful APIs follow the Representational State Transfer (REST) architecture outlined by Roy Fielding in 20001. It uses a subset of HTTP and is what we’ll be defining in this tutorial.
SOAP stands for Simple Object Access Protocol and uses XML as a format to transfer data. It outlines message structure and methods of communication. To define a SOAP API, you use the Web Services Definition Language (WSDL), a machine-readable document, to publish a definition of its interface2.
Apache Kafka is “… an open-source distributed event streaming platform …”3. It uses a series of APIs to facilitate the communication for applications with event-driven architectures (EDA).
Websocket APIs use the Websocket API exposed in browsers to open up a two-way interactive conversation between the browser and API (server) 4. WebSocket APIs are used for event-driven driven architectures and are the focus of the AsynchAPI initiative.
MDN has a wonderful guide on writing WebSocket servers.
AsynchAPI is an organization building tools and authoring the AsynchAPI specification for event-driven architectures. They plan to create the industry standard for asynchronous API specifications.
XML-RPC, “… is a protocol that uses a specific XML format to transfer data compared to SOAP that uses a proprietary XML format.”2
JSON-RPC, “… is similar to XML-RPC but instead of using XML format to transfer data it uses JSON.”2
Researching the API Ecosystem
As you can see from the above list, there are many different API protocols, platforms, architectures, and specifications. Before designing an API, check whether your design approach applies to the type of API you wish to design.
Additionally, best practices and tools change, so there might be a better (or different method) depending on API type.
How to Design an API
There are few concepts that I want to prime your mind with before we start the design project. They’ll serve as guidelines, in addition to other principles and best practices.
Think About Consumers
First, we are designing an interface. It’s for applications, but other people (consumers) need to understand and navigate the interface. The author Peter Morville outlined several adjectives to think about when designing user interfaces. However, they can help us with API design, too5.
These adjectives (part of the UX Honeycomb) remind us to think about the other parties in the design process.
Second, an API route works like a function. Therefore, you interact with it like a contract:
- The API defines the resource’s location, who can access it, and what you provide to receive the resource.
- The consumer locates the resource and (if they can access it) provide the required parameters.
Similarly to programming, the function (API route) should not do more than is necessary. Ideally, a route/method combination should do one thing. Later, we could add parameters to enable subsequent features, but we want the function to be simple.
In the next section, we’ll build an example API specification!
We will use the Swagger open-source editor to build the API specification describing the REST API for this tutorial. Downloading and starting the editor is slightly different from other applications because it is hosted on Github. To complete the project, you’ll need:
- To download Git or have it installed on your local system to pull down the editor code.
- NodeJS and NPM installed
- Familiarity with opening a command prompt or terminal on your operating system
The editor and coding the specification is a bonus. However, it’s fine to follow along without downloading the editor. You’ll be able to follow the process either way.
1. Set-Up Editor
Open up a terminal and navigate to the location where you want to download the Swagger Editor (I’m in my
Desktop folder). In the terminal, clone the repository by running,
git clone https://github.com/swagger-api/swagger-editor.git
Start the Editor
To start the local server, enter the command
npm start in the terminal. The editor should open in a new browser tab.
The project starts with the Swagger Pet Store example YAML file loaded. The left panel is the editable YAML file. The right panel is the auto-generated API documentation based on the values from the left panel.
If you’re new to programming—or working with an editor—you’re not going to be comfortable with this. That’s ok. We won’t use all the properties and will start by describing a much simpler API.
On the top toolbar, click File, and in the dropdown, select Clear Editor. This will give us a blank slate to start with.
The rest of the tutorial will define properties on the spec based on the OpenAPI v3 standards. To learn about the different objects that we define, visit https://swagger.io/specification/ and use the side navigation bar to Specification -> Schema.
2. Add Info
After clearing the editor, we use the navigation bar’s Insert option to add blocks describing our API to the document.
Select Insert and in the dropdown, click Add Info. A pop-up will appear with inputs for describing important information about the API. Enter the following values:
- Title – How to Design an API
- Description – ‘This is an example API spec for a tutorial on How to Design an API. It will be minimal and is under development.”
- Version – 1.0.0
- Name – Admin
- Email – firstname.lastname@example.org
After clicking the Add Info button in the pop-up’s bottom right, the information is added to the editor’s YAML file on the left panel.
Ignore the paths error in the right panel. It won’t go away until we get to that part of the spec. Also, notice that the documentation is updated with the values from the spec. This is a great motivator as we continue to design the API!
As a final note for the info section, there are more values that we could add. However, for this tutorial, we’re keeping it short. Again, you can learn more about the specification at swagger.io/specification.
3. Identify Resources
One of the core principles for API design is the identification and level of attention paid to resources. Resources, also known as Models, help us define our API’s routes, actions, and hierarchy.
APIs are often built with exposing data in mind. Hence, the usage of Models or Collections when describing resources. It’s easy to start modeling our API like our database, but there is—actually—plenty of flexibility in determining the combination and layout of resources and methods6.
Our API (titled “How to Design an API”) has two resources, Principles and Best Practices. If you read the article on API design principles and best practices, you may remember that best practices are often formed out of principles. Therefore, our database may have Best Practices as a property of Principles. Consequently, we might feel the urge to design our API with URLs like
A different design may place both resources at the top level.
Although both are correct and usable, we’ll take the first approach, offering the resources as collections and the ability to query for a resource by ID.
4. Create Paths
Let’s add the
/principles path. Click the Insert button in the top toolbar and select Add Path Item. This is the same process we took when adding the Info object.
In the new pop-up, enter the below values:
- Path –
- Summary – API design principles
This path is for all of the Principles (also known as the Principles collection). Next, we’ll add a few methods (also known as operations) for this path.
First, let’s add a GET operation for fetching principles. Although we could add all the methods to this path that are available (PUT, PATCH, POST, DELETE), we should be concise in our definition.
Consequently, it could be a waste of time defining a method that we aren’t certain will be used.
Again, access the Insert dropdown and select Add Operation from the options. In the modal, add the following values.
- Path – Select the only path available (/principles)
- Operation – select get
- Summary – Retrieve all of some of the principles
- Description – Specify with query parameters whether to return all principles, a single principle, or a range of principles.
- Operation ID – getPrinciples
Click Add Operation, and observe the new path in the editor and on the documentation page.
As previously mentioned, we need to define query parameters that will allow the user to retrieve all, some, or one of the principles. You can define parameters at the path level (useful for headers and cookies), but we choose to define them at the operation level.
Unfortunately, there isn’t an interface or dropdown for adding parameters. We must add them manually.
Add the following code underneath the
operationId but before the
parameters: - in: query name: id required: false schema: type: string description: ID of article - in: query name: offset required: false schema: type: integer description: The number of items to skip before starting to collect the result set - in: query name: limit required: false schema: type: integer description: The numbers of items to return
In the code, we add three optional parameters to this route:
- ID – allows users to fetch a principle by the ID
- Offset and Limit – allows users to paginate requests
There’s plenty more information to add in the parameters section depending on your situation. The documentation for OpenAPI explains many different setups for your route parameters.
Our path parameters are simple. They are integers and strings. However, they could be UUIDs (unique IDs) or dates. It’s best practice to add examples for parameter values to the specification and documentation.
parameters: - in: query name: id required: false schema: type: string description: ID of principle example: "0d786fhy4" <--- new - in: query name: offset required: false schema: type: integer description: The number of items to skip before starting to collect the result set example: 0 <--- new - in: query name: limit required: false schema: type: integer description: The numbers of items to return example: 15 <--- new
For now, we are done with the GET method. We’ll revisit the method later when we talk about responses. Next, we are going to add two more methods for
/principles, POST and DELETE.
You can add the starting code for the POST method using the Insert dropdown or copy and paste the below code underneath the GET method. Swagger double-checks the indentation for the document, and you may need to make adjustments when copy and pasting.
post: summary: Add a principle to the collection description: '' operationId: addPrinciple requestBody: description: Principle object content: application/json: schema: $ref: '#/components/schemas/Principle' required: true responses: default: description: Default error sample response
The POST method is for adding resources or adding resources to a collection. Therefore, we expect the request to contain a
requestBody with data for the new principle.
You should have an error because the
'#/components/schema/Principle' object doesn’t exist yet in the document. The components section is a way for us to reuse objects, parameters, security protocols, and more to declutter and design more efficiently.
Next, let’s add the
Principle schema as a component so we can reuse it. Paste the following code at the bottom of the document.
components: schemas: Principle: required: - id - name - description type: object properties: id: type: string name: type: string description: type: string
The error disappears, and a schema appears in the documentation!
Finally, we can move to our last operation before doubling back to add additional helpful information.
Using the Insert button dropdown, add a delete method with the following information.
- Path – Select the only path available (/principles)
- Operation – select delete
- Summary – Delete a principle
- Description – Deletes a principle by ID
- Operation ID – deletePrinciple
Then, copy the following parameter value for the article ID and past it under
parameters: - name: id in: query description: ID of principle to delete example: 0d786fhy4 required: true schema: type: string
We have done quite a bit of work on the specification. Still, we have more important information to add. In the next section, we’ll discuss providing responses.
5. Add Responses
Responses, including their status codes, schemas, and examples, make up some of the most vital information when designing an API. They represent what the consumer wants to obtain through the API.
We can design common response values and add them to the spec for each operation in a path. It’s important not to go overboard when defining responses. Many status codes can speak for themselves, so we want to pinpoint response cases that are desirable or are unusual when interacting with the API.
First, let’s add a 200 – Success response. Currently, inside the GET definition for
/principles exists a
responses object with the text:
responses: default: description: Default error sample response
default object (and the child description parameter) with this code:
200: description: Success fetching all principles content: application/json: schema: type: array items: $ref: '#/components/schemas/Principle'
There are a few things to take note of:
200represents the status code of the response
contentwe detail the response content type (i.e., text, XML, JSON)
- The response is an array of Principles. The specification states that by reusing the Principle model defined earlier.
Next, let’s add one more response. A user may get a 404 – Not found error in two different scenarios. They mistyped the URL, or the API cannot find the ID of the Principle. To make things clear as to which they are receiving, let’s define a 404 response, as well. Place the above code underneath the 200 response:
404: description: Couldn't find article content: text/plain: schema: type: string example: 'Article not found.'
This response uses a different content type. Also, it provides an example. Adding an example enables the documentation to display a schema or example.
The POST operation creates a new resource with the API. Therefore, the only example response that I want to provide is a 201 – Created response.
With the GET request, we replaced the default response. Conversely, we can leave it in the definition as a default.
201: description: Created a new principle content: application/json: schema: $ref: '#/components/schemas/Principle'
Add the 201 response above the default response block. Furthermore, add an example response for the 201 Principle schema.
Update the response to include an example.
201: description: Created a new principle content: application/json: schema: $ref: '#/components/schemas/Principle' example: id: 'd4fhdu7d' name: Platform Independence description: Interacting with an API should not depend on the type of client application. In other words, the API’s internal workings should not reject client interactions because of the operating system or programming language.
After adding the response, we have three routes with parameters, schemas, responses, and some response examples. Additionally, the editor automatically generates our design in to formatted documentation.
We have come a long way in designing and documenting our API. However, API design does not take place in a vacuum. What I mean is we need to collaborate and work with others. This ensures our design is practical and useful.
There are tools and ways to collaborate on an API with other members of your team. Although we have designed a few aspects of the API, we need to validate our decisions with others. One feature of creating an API specification is portability. To export our definition to a YAML file, click the File option in the top toolbar and select Save as YAML—the file downloads to your computer.
I hope this tutorial introduced you to designing a REST API in a simple and useful way. However, there is still plenty to learn about designing or describing an API. For practice, you can try to add a Best Practices resource to the API definition. Or, explore how to specify API security in the definition. Thanks for reading!
1 “Representational State Transfer.” Wikipedia, 28 Mar. 2021, en.wikipedia.org/wiki/Representational_state_transfer#History. Accessed 1 Apr. 2021.
2 RapidAPI Staff. “Types of APIs (and What’s the Difference?)  | RapidAPI.” The Last Call – RapidAPI Blog, 7 Mar. 2019, rapidapi.com/blog/types-of-apis/.
3 “Apache Kafka.” Apache Kafka, kafka.apache.org/. Accessed 30 Mar. 2021.
4 “The WebSocket API (WebSockets).” MDN Web Docs, 28 Nov. 2019, developer.mozilla.org/en-US/docs/Web/API/WebSockets_API. Accessed 30 Mar. 2021.
5 Agrawal, Prashant. “Design APIs like You Design User Experience.” Medium, 5 May 2019, medium.com/better-practices/design-apis-like-you-design-user-experience-a7adeb2ee90f. Accessed 31 Mar. 2021.
6 “Resource Oriented Design | Cloud APIs.” Google Cloud, cloud.google.com/apis/design/resources#resources. Accessed 1 Apr. 2021.