I'm developing a REST Api using Node.js & Express.js. Recently I've started looking towards best practices for REST Api design but it's bit confusing so please bear with me.
For Example:
Lets say I have the following API Collections:
- Users
- Tasks (every task must belong to a User)
And Have these resources:
- GET
/api/users
returns all Users - GET
/api/users/:id
returns a specific User - POST
/api/users
will create a single User - POST
/api/users/:id
will update a specific User - POST
/api/tasks
will create a new Task but will assign it to the loggedIn User. - POST
/api/tasks/:id
will update a specific Task - GET
/api/tasks
will get all Tasks - GET
/api/users/:id/tasks
will get all Tasks for a specific User - GET
/api/users/:id/tasks/:id
will get a specific Task for a Specific User
Now each "Resource" above is redirected towards a "Controller" that actually performs actions.
For example:
POST ('/api/users', myController)
let myController = (req,res) => {
Create new User
Save to Database
Call Private Send an Email to Admin function etc
}
Questions:
Lets say I have to perform some queries on a resource such as filter
should I:
OPTION 1:
Create a new resource like so /api/users/:id/tasks/filter
and within my Controller and perform the filtering actions on the database within the controller
OPTION 2:
Use Query Strings like so /api/users/:id/tasks?filter=title::someValue&priority::someOtherValue
. According my knowledge doing this will NOT create a new resource but will simply call the /api/users/:id/tasks
resource with the above Query Strings.
If I follow OPTION 2 thats means I will only have one Controller that manages the /api/users/:id/tasks
actions so to handle any Queries for this Resource should I do the following:
GET ('/api/users/:id/tasks', myController)
let myController = (req,res) => {
if(req.filter)
{handle Query here and get data from database and return to user}
else if(some other query)
{handle query here}
else if(some other query)
{handle query here}
else {
if there are NO queries then get get all Tasks for a specific User.
}
}
Is the above implementation/concept correct? To me it seems messy and confusing. So what am I doing wrong here? What's the correct way of handling queries? Should a single Controller for a resource handle everything for that particular resource?
I'm having trouble thinking in "REST" there is so much information I'm not sure what right and whats wrong. So my basics are a bit weak so any guidance towards that would be very helpful (any articles or online resources).
Best Answer
Disclaimer
Despite the question has been scoped as REST, the truth is that it has nothing to do with REST.
Long story short, how to map the request parameters to an specific query language is implementation details. So, for now, lets leave REST aside.
For us to say whether is correct or not, we would need to be familiar with the project and its requirements. So far, what we can say is: the solution is not flexible.
Flexible in the sense of scaling in the long run. Every new
filter
would take you (at least) to modify thecontroller
, the service layer and maybe the data access layer too. From the management standpoint, is hard to buy so much work for so little feature. In other words, the maintenance looks expensive.Nevertheless, it depends on how often you add new parameters to the query.
You are missing two possible abstractions.
Note: Some frameworks makes #1 optional.
Right now the mapping is hardcoded as a
if/else
block. Too rigid for scaling well.Instead, It could be similar to:
Making the mapping more dynamic and reusable.
At this point, my first advice would be look for libraries compatible with your framework or programming language.
My second advice would be, pick the one that supports Convention over configuration.
Even if It doesn't prevent you from making changes, at least (I'm sure) the costs of the changes will be lesser than they are now.*
* If we consider the code given as example in the question.