DEV Community

Richard Igbiriki
Richard Igbiriki

Posted on • Updated on

Building Consistent RESTful APIs

When it comes to building RESTful APIs, the applications are limitless while the standards and/or best practices remain the same across different stacks. This is an attempt to portray some of those standards and best practices (highly opinionated).


Representational State Transfer (REST) effectively invalidates sessions and as such, our applications require another way of authorizing users trying to access it. One of the most commonly used tools for authorization is JSON Web Tokens-JWT. Once a user logs in or signs up to our application, a token is assigned to that user for a period of time (the length depends largely on the security concerns of your application). A good practice is to add a unique identifier to the token so each request to our application can serve the particular user making that request without asking for extra parameters from the user.

For example, if I am logged in using JWT, and I attempt to retrieve all transactions I have made on the application, the application should be able to provide ONLY my transactions based on my JWT authorization token.

Without going into implementation details, one can achieve this by having a service intercept incoming requests, process the JWT token and set a current_user object or variable accessible throughout the application.

Resources, Actions, and HTTP Methods

In REST we have resources, which can represent database tables, e.g companies, users, posts, messages, etc. Every resource has a list of actions that can be performed on it. These actions are determined by the HTTP method on each request to that resource. HTTP Methods are also referred to as HTTP VERBS because they perform/determine actions. Given a resource, transactions, below are some of the most common corresponding HTTP Methods and actions.

GET - /transactions (get all transactions)
GET - /transactions/:id (get a single transaction)
POST - /transactions (create a new transaction)
PATCH - /transactions/:id (update parts of a transaction)
PUT - /transactions/:id (update a transaction)
DELETE - /transactions/:id (delete a transaction)

Depending on the scale of your application, you may need to extend the actions available on your resource by custom endpoints. Custom endpoints can either affect the entire resource (collection) or a member of the resource. For actions affecting the collection, it is as follows:

GET - /transactions/users (get all users that have made transactions)
GET - /transactions/failed (get all failed transactions)

For a member, assuming we have a separate but related resource for a transaction details:

GET - /transactions/:id/details (get the details of a transaction)
POST -/transactions/:id/details (create details for a transaction)
PUT -/transactions/:id/details (update the details of a transaction).

HTTP Response Status

The response and status returned from every request depending on the action being performed and the status of the action. A POST request should never return a 204 (No Content) and a DELETE request should never return 201 (Created). Below are the most common responses based on their request and status.

200 - OK

The request was completed. You can't go wrong with a 200 status if the action requested for was completed successfully however, there might be more appropriate status messages depending on the action.

201 - Created

Ideally, this should be returned whenever a new object is created (mostly with a POST request) alongside the object that was created. Returning the object that was created is a personal preference as it grants access to the newly created object immediately.

204 - No Content

This can and should be returned after a DELETE has been completed on an object.

401 - Unauthorized

This is appropriate when a user tries an action that requires authorization but the request doesn't possess that authorization.

404 - Not Found

Highly unlikely but if a user requests for a resource that doesn't exist, 404 is the appropriate status to respond with. Albeit based on your use case, returning 200 - ok and an empty result may be more appropriate.

Top comments (7)

kevinhch profile image

Hey, why did you accept :id in a DELETE transaction, I mean, this is secure? Imagine I have the token to send a request to your server, and now I can delete all the users using a simple ID from my browser, is this good? I prefer to use :id just in GET request

richardigbiriki profile image
Richard Igbiriki

Yes, I think DELETE /transaction/:id is the best way to delete a particular record. You are not deleting the entire table but only a particular record if it exists.

Another thing to keep in mind, you can have authorization checks to prevent certain users from being able to perform certain actions. You can also prevent certain action types (DELETE) from a resource.

So in your case, if you don't want Users to be deleted, you can prevent any access to DELETE users/:id.

binarypatrick profile image

Never heard of the UPDATE verb and don't see it in any specs..? Am I missing out on something here?

anthofo profile image

Me neither, maybe it's the PATCH verb, and PUT creates or replaces a whole ressource, so it would give sth like:

  • PUT: update a transaction
  • PATCH: update a part of a transaction
richardigbiriki profile image
Richard Igbiriki

Yes! Thank you, it is PUT and PATCH.

nepz_sonze profile image

I think even the /detail is not needed. "/transactions/:id" sounds fine.

richardigbiriki profile image
Richard Igbiriki

This is entirely optional and depends solely on your implementation choice. You could have a Transactions table with minute information about each transaction and a Details table with more detailed information about each transaction. I am using this approach of two related tables.

Yes, you can also get all the details in /transaction/:id but it depends on the use case.