Custom methods in REST API endpoints

Custom methods in REST API endpoints

HTTP approach proposes such methods to handle resources:

POST for creation.

GET for retrieving information.

PUT for replacing a resource with the given content.

PATCH for partially updating a resource.

DELETE for deleting the resource.

There are other HTTP methods like HEAD or OPTIONS but mostly they are either generated by frameworks or used only due to technical concerns. So, we’re not discussing them in this article.

Mostly these methods are sufficient to build RESTful API. However, some cases do not align well with predefined methods.

In this article, I’m telling you:

The problem with mapping business operation to a set of predefined HTTP methods.
Ways to implement custom methods.
Pros and cons of each solution.

The problem with mapping business operation to a set of predefined HTTP methods

Consider we’re developing an email service. A user created a draft and now they want to send it. What HTTP method should you apply in this case? There are multiple options:

POST /drafts/{draftId}

Well, if you send a draft with POST, then how do you create a new draft?

PATCH /drafts/{draftId}

{ “status”: “sent” }

In this case, we don’t introduce any specific endpoint. There is one that updates the draft resource content. Therefore, we can call it to change the draft status to sent value. It supposes to trigger mail sending.

The advantage is that your API remains concise. But there are also crucial drawbacks:

The flow might be misleading for a client. It’s not really obvious that such an operation should send a draft.
The approach is error prone from a client’s perspective. If a client sends status field by mistake, it might trigger draft sending. Even if a client just wanted to update the draft content.
The code on the backend also becomes more complicated. Developers have to add if cases for the endpoint that triggers draft sending if there is a particular field in the request.
Authorization for sending a draft can be different from a regular draft update. Again, more complicated scenario on the backend.
Sending a draft can have different semantics. For example, if a user triggers draft sending, the server might return some jobId representing the status of the operation. But regular PATCH invocations should occur synchronously. Meaning that return values also differ, which makes API more complicated.

The example assumes that there is a single resource draft. Other implementations can have different resources for drafts, received emails, and sent emails. But I skip these details for the sake of simplicity.

I’ve described a rather simple scenario. And even this one requires some sort of custom methods. Let’s discuss possible implementations.

Sub-resource

This is the most straight-forward one. I bet you’ve seen such examples often. Look at the code snippet below:

POST /drafts/{draftId}/send

It’s a separate endpoint. So, we don’t have to deal with PATCH overlapping. The problem here is not about implementation, but REST API principles. A path describes a resource. And it should be a noun. But here the send word is a verb that is a marker for an action. It raises bizarre questions:

What is a send? Is it an entity for a given resource?
Can you invoke GET /drafts/{draftId}/send?
Can you transfer send entity to another draft?

You may think that I’m just making these claims out of nowhere. But imagine that a new person comes to your project. If your product is small and you don’t have many endpoints, then probably nothing will go wrong. But if your services exposes dozens of endpoints and many of them have custom methods in the API, then a new business analyst or architect can waste time figuring out the meaning of that sub-resource that actually has never existed.

As a matter of fact, I recommend you to avoid this solution.

A special actions endpoints

If you have custom methods, why can’t it be a dedicated resource? Look at the code example below:

POST /drafts/{draftId}/actions

{ action: “send” }

That looks more intriguing. The pros are:

This endpoint fits well with REST API philosophy.

GET can return the list of all available actions. You can even add authorization (some actions are not available for specific users).
In some cases, updating existing actions with PATCH or PUT might be useful.

Still, there are some cons:

All custom methods map to a single endpoint. Which adds problems to the backend (complex scenario, extra if statements, complicated authorization, etc.).
Some actions require additional parameters. If you document your REST API with OpenAPI (which you should do), then you would have to distinguish parameters for different action with oneOf/anyOf clause. Some client generators have problem with dealing these conditions correctly. Therefore, unnecessary technical concerns occur.

I can say that this approach is valid and might be effective. But usually it’s an overengineering.

Custom method with a query parameter

Look at the example below:

POST /drafts/{draftId}?method=send

I’m not stopping here because this approach is almost identical to the one with PATCH that we’ve seen before. Pros and cons are the same (additional if statements on the backend, unexpected errors on the client side due to wrongly passed parameters etc.). So, let’s move forward.

Custom method with a colon punctuation mark

This approach is similar to sub-resource one, but has different semantics. Look at the code snippet below:

POST /drafts/{draftId}:send

The idea is simple:

You declare all custom methods as POST ones.
Then you add the verb describing the custom operation divided from the resource path by a colon.

I see many advantages of such technique:

A custom operation maps as a dedicated endpoint. Meaning that a generated client will have a separate function for sending a draft.
Because of the presence of a separate endpoint code on the backend becomes easier (authorization, returning values etc.).
You don’t have to worry about overlapping with existing regular resources.
A colon punctuation mark clearly puts a difference between a resource and an action.

I’ve taken this approach from the book API Design Patterns. I strongly recommend you to read it if you deal with creating REST API in your daily work. There are lots of useful patterns besides custom operation methods.

Conclusion

Custom methods are necessary for the majority of applications. But you should apply the tool with caution. Don’t fall into the trap of creating too much custom methods. That makes your RESTful API actually RPC. However, if you need one, you know how to design it.

Thank you very much for reading the article! I hope you’ve learnt something new and it will help you to complete the work more efficiently. I’d be grateful, if you pressed a like button, left a comment and shared the piece with your friends and colleagues. Have a nice and productive day!

Resources

HTTP methods
RESTful API
OpenAPI
OneOf/AnyOf/AllOf/Not clauses
API Design Patterns book

Leave a Reply

Your email address will not be published. Required fields are marked *