DEV Community

loading...

Create Reliable & Easy-to-use APIs with API First Design

raphael_jambalos profile image Raphael Jambalos Updated on ・7 min read

Recently, I had to create an API Gateway for a customer. Coming from a developer background, API design was an afterthought for me. Once I was done with the user stories, I wrote my database schema so I could go ahead with coding the web application. The API develops organically, from whatever makes sense at that time.

But as I learned on the project, APIs are interfaces between people (as much as they are between systems). Deliberately thinking about your API design and being consistent about these design decisions help your end-users learn your APIs faster and with minimal effort. Also, your end-users rely on your APIs to create applications. Breaking changes to these APIs means downtime for your end user's applications.

Post Summary

In this post, we will discuss how the API-first mindset encourages us to put our end-user first so they can count on a simple, reliable, and consistent API.

In summary, the process looks like this:

  • Part 1 - Create Reliable & Easy-to-use APIs with API First Design (current post)
    • Start with the goal
    • Create user stories
    • Build the domain models
    • Sketch your APIs
  • Part 2 - Define API contracts with Swagger / OpenAPI
    • Write the OpenAPI definition
    • Start Developing
    • Handling API Design Changes

Using OpenAPI 3.0 definitions as "contracts" between API developer and his/her end-users, a single source of truth is established on how the API should operate. With this "contract" defining how each endpoint of the API operates, we safeguard against sudden unplanned changes on the API.

Alt Text


API-First Design Process

Let's go through the design process for a simple loyalty application we will be creating for restaurants.

[1] Start with the goal

Our goal for the loyalty application is for partner restaurants to create loyalty cards for customers and for customers to be able to earn points and redeem items with their points.

Setting the goal sets the stage for the next steps of the process. It also helps you scope out what your API really does. If you need to develop features that aren't aligned with this goal, then you should probably consider splitting it off to a separate application.

[2] Create user stories

With this goal in mind, let's elaborate on the capabilities of our loyalty application using user stories:

  • As an end-user:
    • I can register for a loyalty card online
    • I can sign in
    • I can earn points with every transaction with a partner restaurant
    • I can view my points balance online
    • I can view my transaction history online
    • I can view specific transactions that I made
  • As a restaurant manager:
    • I can see online card registrations for my restaurant
    • I can print a card for each registration
    • I can mark the card as claimed
    • I can view transactions consummated with the loyalty card for my restaurant
    • I can use a user's points to transact
  • As an admin user:
    • I can create restaurants

[3] Build the domain models

Building your domain models is the process of translating your user stories into objects. These objects have attributes and behaviors. Think of these classes as similar to classes in the Object-Oriented Programming (OOP) paradigm.

Alt Text

With the objects in place, let's think about their relationships with one another:

  • User can have many cards
  • Card can have many transactions
  • Partners can originate many cards (they can sell as many loyalty cards as they want)
  • Partners can have many transactions

To operationalize these relationships, we will introduce the concept of "foreign key". With a foreign key, we add the field {object_name}_id to the object at the right side of the relationship. For the "User can have many cards" relationship, we add the field user_id on the Card object.

Alt Text

[4] Sketch your APIs

Once you have the domain models ready, it's time to sketch your APIs. Resist the urge to work at your web application right away. Take the time to think about each API endpoint you are going to expose and the resource layout of your APIs.

Alt Text

Sketching your API forces you to imagine how your API is going to look before you put in the long dev hours. After working on the sketch, show this design to your API stakeholders and get their feedback. At this stage, major changes will be almost costless to do because no code has been implemented yet.

Understanding API endpoints

To call an API, send a request to the server with the following components:

  • base URL: loyalty-app.com
  • path: /cards
    • How we construct our paths determines how our resource layout is going to be. An alternative resource layout for the Card resource is to nest it with the User resource, such as /users/10/cards.
  • HTTP method: GET
  • query parameters: ?name=Raphael
  • request body:
    • It is used for POST and other HTTP methods, but not GET.
  • headers

For our example above, when you join them together, the request should look like this:

  • GET loyalty-app.com/cards?name=Raphael

API Best Practices

Here are a few guidelines. Most of these tips are not hard rules. At the end of the day, you will have to decide what is best for your API. But whatever you decide, you should aim to be consistent in your API design.

Standard Methods

  • Standard methods allow APIs to have a consistent set of methods across different resources.
    • Standard methods are patterned after CRUD (Create, Read, Update, Delete). A list of the standard methods is available below.
    • Standard methods should have no side effects. It does what it says, nothing more. This way, we can have a consistent set of expectations across all standard methods. For example, the POST /loyalty-cards endpoint creates a loyalty card, it should not create a transaction as well.
    • It’s preferable to use PATCH over PUT when updating only certain parts of an object.
    • If you're not going to implement all standard methods of a resource, you should still build an endpoint for it but have it return HTTP 405 (method not allowed) or HTTP 403 (forbidden). This way, your API is consistent.

Custom Methods

  • Custom methods allow you to create endpoints with side effects. It has the format /{resource}/{id}:{custom_action}
    • For example, the POST /loyalty-cards/10:claim_card

Use HTTP Status Codes

  • Use HTTP status codes to communicate meaning. Not all errors are error 500 and not all successful operations should be HTTP 200.
    • HTTP 200:
      • HTTP 201 (Created): After creating a resource
      • HTTP 204 (No-Content): After deleting a resource
      • HTTP 200 (OK): Catch-all for successful operation
    • HTTP 400: User error - the user tried something wrong
      • HTTP 403 (Forbidden): When you don't have access to the resource you are accessing
      • HTTP 404 (Not Found):
      • HTTP 400 (Bad Request): Check the full list of HTTP 400 codes, if it's not there use this as a catch-all
    • HTTP 500: Server error - the fault is with the server
      • HTTP 500: Internal Server Error - pretty much a catch-all for all application errors.

Different approach for long-running operations

  • As a best practice, these guidelines apply to API endpoints that consistently return faster than 30secs. If your endpoint takes longer to respond, you will keep your end-users waiting and this will result in a subpar user experience. Instead of using the fast response paradigm we have introduced thus far, you may have to look into the asynchronous processing paradigm.

That's a quick summary of the API guidelines. If you want to learn more, I highly recommend the book API Design Patterns by JJ Geewax.

For a complete listing of standard endpoints, here is a listing using the cards resource:

standard - collection endpoints

  • CREATE - POST /cards
  • READ ALL - GET /cards

standard - resource endpoints

  • READ ONE - GET /cards/10
  • UPDATE ONE (override object) - PUT /cards/10
  • UPDATE ONE (override specific methods) - PATCH /cards/10
  • DELETE ONE - DELETE /cards/10

Let's apply what we have learned

Our task now becomes determining our API resource layout and translating the methods of each class into an API endpoint.

For the Cards class, we chose to nest it to the users resource. Hierarchial relationships like this are created when the nested resource is owned by the parent resource. This means if we delete our user #10, we also delete all the cards associated with it. If user #10 abused our fair use policy, we disable all his cards, and so on.

Alt Text

What's next?

Now, we have successfully created our API design sketch. At this stage, I suggest you show this sketch to your API stakeholders. Get their feedback and iterate based on that.

In the next post, we will be creating an Open API 3.0 definition to define the details of each API endpoint. With this definition, we can have a mock

Special Thanks

Special thanks to Allen for making my posts more coherent. This blog post is also made possible by the authors below who have made learning APIs a joy.

Discussion (10)

pic
Editor guide
Collapse
petemcfarlane profile image
Pete McFarlane

I’ve never seen custom resource methods like that (with a colon after resource I’d), but I quite like the idea. Do you have any examples of other APIs that use that technique? I’m wondering how widespread it is. Thanks

Collapse
raphael_jambalos profile image
Raphael Jambalos Author

Hi Pete! I got the idea from JJ Geewax's API Design Patterns Book. He is a software engineer at Google, working on GCP. Google Cloud Platform is one of the companies using this now: cloud.google.com/apis/design/custo...

I like the idea too. By using colons instead of the famous slash character, we get to have a clear separation between the resource layout (/users/10/transactions/11) and the custom method (:mark_as_paid, with the whole URL being /users/10/transactions/11:mark_as_paid).

It takes some getting used to, but I think it results in better, more consistent API design.

Collapse
robencom profile image
robencom

I especially appreciate the HTTP status codes part.

Someone where I work, actually told me that other developers would "laugh at us" for using any status code besides 200 OK. He thought that returning result codes (RC) and result messages (msg) is enough info for other developers, and when it comes to status code, he thinks if the request passes through, then it should be 200 OK.

Obviously this is PAINFULLY wrong, and it was more painful because that person was treated like a GOD in the company, and his title had the words "Expert Web Engineer" in it...

Nevertheless, I managed to convince him with proofs from all around the world.

Collapse
raphael_jambalos profile image
Raphael Jambalos Author

Hi Rob! I totally agree with you! When I did API testing for a client, it really annoyed me to see all of their APIs returned HTTP 200, even if there was an error! They just use the presence of the key "error_message" on the response to communicate that there was an error.

I think this is a complete waste of the HTTP code functionality and how a lot of HTTP clients are already preconfigured to handle 200s, 300s, 400s, and 500s differently.

Collapse
highcenburg profile image
Vicente G. Reyes

Thanks, Raphael! This article somewhat gives a clearer picture of what I'm building now. I knew I was missing a step that's why I'm all over the codebase🤣

Collapse
raphael_jambalos profile image
Raphael Jambalos Author

Hi Vicente! Glad that the article was helpful to you. Let me know if there's anything else I can help with!

And happy to see more Filipino kababayans here in Dev.to! 🇵🇭

Collapse
highcenburg profile image
Vicente G. Reyes

Sure man! Looking forward to more articles from you! 🇵🇭 🇵🇭 🇵🇭 🇵🇭

Thread Thread
raphael_jambalos profile image
Raphael Jambalos Author

Thanks Vicente!

Collapse
rahoulb profile image
Rahoul Baruah

I love the fact that you talk about the person. It might be a computer doing the talking but it’s a person writing the code.

Collapse
raphael_jambalos profile image
Raphael Jambalos Author

Hi Rahoul! Thank you for the comment. I would like to think of API as an interface between people (tech teams) as much as they are between systems. Looking at the APIs we create as the "gateway" of other teams into our systems or companies can really go a long way in appreciating why good and consistent API design is a must.