Custom methods in REST API endpoints
Tags: api, restapi, http, web
Published: 2024-05-01 18:30:49

HTTP approach proposes such methods to handle resources:

1. `POST` for creation.
1. `GET` for retrieving information.
1. `PUT` for replacing a resource with the given content.
1. `PATCH` for partially updating a resource.
1. `DELETE` for deleting the resource.

> There are other HTTP methods like [HEAD or OPTIONS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods) 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](https://www.redhat.com/en/topics/api/what-is-a-rest-api). However, some cases do not align well with predefined methods. 

In this article, I'm telling you:

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

![The article meme cover](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/225bc2ln9yc9oq4ueb3a.jpg)

## 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:

1. The flow might be misleading for a client. It’s not really obvious that such an operation should send a draft.
1. 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.
1. 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.
1. Authorization for sending a draft can be different from a regular draft update. Again, more complicated scenario on the backend.
1. 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:

1. What is a `send`? Is it an entity for a given resource?
1. Can you invoke `GET /drafts/{draftId}/send`?
1. 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:

1. This endpoint fits well with REST API philosophy.
1. `GET` can return the list of all available actions. You can even add authorization (some actions are not available for specific users).
1. In some cases, updating existing actions with `PATCH` or `PUT` might be useful.

Still, there are some cons:

1. All custom methods map to a single endpoint. Which adds problems to the backend (complex scenario, extra `if` statements, complicated authorization, etc.).
1. Some actions require additional parameters. If you document your REST API with [OpenAPI](https://www.openapis.org/) (which you should do), then you would have to distinguish parameters for different action with [oneOf/anyOf clause](https://swagger.io/docs/specification/data-models/oneof-anyof-allof-not/). 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:

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

I see many advantages of such technique:

1. A custom operation maps as a dedicated endpoint. Meaning that a generated client will have a separate function for sending a draft.
1. Because of the presence of a separate endpoint code on the backend becomes easier (authorization, returning values etc.).
1. You don’t have to worry about overlapping with existing regular resources.
1. 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](https://www.manning.com/books/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

1. [HTTP methods](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods)
1. [RESTful API](https://www.redhat.com/en/topics/api/what-is-a-rest-api)
1. [OpenAPI](https://www.openapis.org/)
1. [OneOf/AnyOf/AllOf/Not clauses](https://swagger.io/docs/specification/data-models/oneof-anyof-allof-not/)
1. [API Design Patterns book](https://www.manning.com/books/api-design-patterns)