Designing Backend APIs in One Shot: Best Practices for Efficiency and Maintainability

When working on a sprint with specific objectives and the need for quick functionality delivery that can be reworked later, it's important to consider the implications of your choices. While your scrum master and project team may appreciate this approach, it can lead to unnecessary rework for both you and your team.

Instead, how about designing your APIs once and forgetting about them? Here are some guidelines that will help you stay ahead of the curve, allowing you to relax and enjoy the benefits of remote work during the next sprint.

Effective Resource Names

We often come across textbook examples of resource names like "Accounts" or "Customers," which generally work well in practice. For instance, when creating a customer, we use the endpoint POST /customers and provide the necessary details in the request body.

Avoid the trap of building functionality with narrow focus, resulting in endpoints like POST /createCustomer. As you delve deeper into your product backlog, you may find yourself creating unnecessary endpoints like POST /fetchCustomer.

Stick to the CRUD (CREATE READ UPDATE DELETE) pattern:

  • Fetching customers -> GET /customers

  • Fetching a specific customer -> GET /customers/{id}

  • Creating a customer -> POST /customers

  • Deleting a customer -> DELETE /customers/{id}

  • Updating a customer -> PUT /customers/{id}

Always pluralize the resource name. Notice that the resource doesn't use GET /customer. At first glance, this might confuse a novice backend engineer into thinking that the endpoint expects a single customer instead of multiple ones. Removing assumptions in programming helps you reach the finish line faster.

Versioning Upfront!

Yes, you should consider versioning right from the start! When creating the initial version of an endpoint, it's tempting to overlook versioning. However, saving time requires adopting a maintainable approach from the beginning.

What does this mean?

So, your GET /customers now becomes GET /v1/customers. Introducing versioning ensures there's no confusion about which version is being used. GET /customers is not as clear as GET /v1/customers, and using the latter saves time during debugging.

Always prefix the version. Imagine introducing the version after the fact and including it in the URI as GET /customers/v1/. In older endpoints, the customer identifier will likely be assumed as v1, causing a nightmare for backwards compatibility.

Soft Deletes, NEVER Hard Delete

There are some situations where hard deletes (permanent data removal) may be allowed, but in general, it's better to provide systems that allow for easy auditability and debugging.

To remove an item after calling the DELETE /customers/{id} endpoint, set the record status to "DELETED" and mark the datetime when the item was removed. This approach indicates that the item has been removed and when it happened. It also helps avoid breaking dependencies.

If we remove a customer record, the account owned by that customer may no longer function as expected. Your product team may also require functionality to display removed customers, so remember to include retrieval functionality in your GET endpoints.

For example: GET /customers?includeDeleted=true

By addressing this early on, you won't need to make adjustments in future sprints, saving you time and avoiding headaches.

Pagination and Sorting

Always consider your fellow teammates, especially the frontend developers who will need pagination and sorting capabilities. By incorporating these features from the start, you reduce the burden on your servers (fetching data) and on your clients (rendering data). Most stacks offer various paging libraries, making implementation smoother.

Therefore, your customer endpoint should support some form of pagination, such as GET /customers?size=x&page=y, and for sorting, use GET /customers?sort_by=last_name.

Previous
Previous

Introduction to Dart 3.x

Next
Next

Monolithic & Microservice Architectures