DEV Community

Alberto Fernandez Medina
Alberto Fernandez Medina

Posted on • Originally published at onlythepixel.com

9

API Routing with Express

Note: This is the 2nd post of a series of post about Building APIs With Express.

Based on my last post about Making APIs with Node and Express I'll continue developing over the generated code.

So, I left the basement for my TODO API prepared. Now it's time to work in the different endpoints and HTTP verbs/methods that this API is going to use.

This post was first published on onlythepixel.com

/v1/tasks

I'm going to start building my API's endpoints with Tasks Collection.

[GET] /v1/tasks

First thing I need is to GET the list of tasks from my so innovative TODO API and I think that the best way to build it is by creating a new isolated express router instance.

src/v1/tasks.js

const router = require('express').Router()

/**
 * TODO: Store data in DB.
 */
let tasks = [
  {
    description: 'Another task',
    isDone: false,
    createdAt: Date.now()
  }
]

router.route('/')

  .get((req, res, next) => {
    return res.json(tasks)
  })

module.exports = router
Enter fullscreen mode Exit fullscreen mode

Easy peasy! Now I need to mount that router on my API, I'm going to remove the old Let's TODO! message:

src/v1/index.js

const router = require('express').Router()
const tasks = require('./tasks')

router.use('/tasks', tasks)

module.exports = router
Enter fullscreen mode Exit fullscreen mode

Let's try the new endpoint (Now that I have implemented yarn I can run the server with `yarn start` instead of `npm start`):
bash
curl -X GET localhost:3000/v1/tasks
[
{
"createdAt" : 1481985039988,
"isDone" : false,
"description" : "Another task"
}
]
``
Note: It's needed to restart the server if there have been changes in the code while it is running to see the changes.

Now I'll continue with the creation of a new task

[POST] /v1/tasks

It should be in the same router instance the GET method is. This time I'll need a new middleware to parse correctly the request body, body-parser (this time I'll install it with Yarn).

`bash
yarn add body-parser
`

Note: Same as execute npm i -S body-parser

And now it needs to be attached to the app

src/index.js

`javascript
const express = require('express')
const logger = require('morgan')
const bodyParser = require('body-parser')
const app = express()
const v1 = require('./v1')

/**

  • Middlewares */ app.use(logger('dev')) app.use(bodyParser.json()) app.use(bodyParser.urlencoded({ extended: true }))

...

_**Note:** It will inject data from the request into
req.body`._

And now the request handler.

src/v1/tasks.js

`javascript
...

router.route('/')

.get((req, res, next) => {
return res.json(tasks)
})

.post((req, res, next) => {
const newTask = req.body

newTask.createdAt = Date.now()
newTask.isDone = false
tasks.push(newTask)

return res.status(201).json(newTask)
Enter fullscreen mode Exit fullscreen mode

})

module.exports = router
`
Note: Take advantage of the HTTP status codes.

Done, let's try again with a POST request this time:
`bash
curl -X POST -H "Content-Type: application/json" --data '{"description": "Also another task more"}' localhost:3000/v1/tasks
{"description":"Also another task more","createdAt":1481986821539}

curl -X GET localhost:3000/v1/tasks
[
{
"createdAt" : 1481986807819,
"isDone" : false,
"description" : "Another task"
},
{
"createdAt" : 1481986821539,
"isDone" : false,
"description" : "Also another task more"
}
]
`

Time to delete! Due this is an endpoint pointing a collection when it receives a DELETE request it should delete all items of the collection.

[DELETE] /v1/tasks

src/v1/tasks.js

`javascript
...

.post((req, res, next) => {
const newTask = req.body

newTask.createdAt = Date.now()
tasks.push(newTask)

return res.json(newTask)
Enter fullscreen mode Exit fullscreen mode

})

.delete((req, res, next) => {
tasks = []

res.status(204).end()
Enter fullscreen mode Exit fullscreen mode

})

module.exports = router
`

Delete all the things!

`bash
curl -X DELETE -i localhost:3000/v1/tasks

HTTP/1.1 204 No Content
X-Powered-By: Express
ETag: W/"2-11FxOYiYfpMxmANj4kGJzg"
Date: Sat, 17 Dec 2016 17:13:07 GMT
Connection: keep-alive
`

I am done with this endpoint.

/v1/tasks/:taskId

Now it's time to handle the single task endpoint. Here I'm going to take advantage of an Express feature to parse URLs, for this case, to treat a segment of the URL as a parameter and assigning it the name taskId.

taskId param

I am going to define a new param for the tasks router to get a certain task passing the task ID in the URL.

src/v1/tasks.js

`javascript
...

router.route('/')

...

router.param('taskId', (req, res, next, id) => {
const task = tasks[id]
let err

if (!task) {
err = new Error('Task not found')
err.status = 404
} else {
req.task = task
}

return next(err)
})

module.exports = router
`

[GET] /v1/tasks/:taskId

Then I only need to reply with the found task in a new endpoint listening to any request aimed to /v1/tasks/:taskId meaning by :taskId whatever comes after the slash (IE: /v1/tasks/my-task-id OR /v1/tasks/01234).

src/v1/tasks.js

`javascript
...

router.param('taskId', (req, res, next, id) => {
...
})

router.route('/:taskId')

.get((req, res, next) => {
return res.json(req.task)
})

module.exports = router
`

Let's try it

`bash
curl -X GET localhost:3000/v1/tasks/0
{
"description" : "Another task",
"isDone" : false,
"createdAt" : 1481996187751
}

curl -X GET -i localhost:3000/v1/tasks/1234

HTTP/1.1 404 Not Found
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 1082
ETag: W/"43a-4d6NK29IKrV0B3jSAdQGvA"
Date: Sat, 17 Dec 2016 17:46:36 GMT
Connection: keep-alive
{
"stack" : "Error: Task not found\n at router.param (/develop/another-todo-api/src/v1/tasks.js:38:11)\n at paramCallback (/develop/another-todo-api/node_modules/express/lib/router/index.js:404:7)\n at param (/develop/another-todo-api/node_modules/express/lib/router/index.js:384:5)\n at Function.process_params (/develop/another-todo-api/node_modules/express/lib/router/index.js:410:3)\n at next (/develop/another-todo-api/node_modules/express/lib/router/index.js:271:10)\n at Function.handle (/develop/another-todo-api/node_modules/express/lib/router/index.js:176:3)\n at router (/develop/another-todo-api/node_modules/express/lib/router/index.js:46:12)\n at Layer.handle as handle_request\n at trim_prefix (/develop/another-todo-api/node_modules/express/lib/router/index.js:312:13)\n at /develop/another-todo-api/node_modules/express/lib/router/index.js:280:7",
"message" : "Task not found"
}
`

[POST] /v1/tasks/:taskId

Now it's important to pay attention here because the POST request to a specified resource in an API REST by definition should override completely the resource, meaning that if:
I do a GET to /v1/tasks/0 receiving:
`json
{
"description" : "Another task",
"isDone" : false,
"createdAt" : 1481996187751
}
`

If I do a POST to /v1/tasks/0 with this data:
`json
{ "isDone": true }
`

The next time I do a GET to /v1/tasks/0 I'll receive the next response:
`json
{ "isDone": true }
`

The proper way a client should make a POST to an API resource is by providing all the resource info in the request to avoid the lose of info.

Now go back to code!

src/v1/tasks.js

`javascript
...
.post((req, res, next) => {
const updatedTask = req.body

tasks[req.params.taskId] = updatedTask

return res.json(updatedTask)
Enter fullscreen mode Exit fullscreen mode

})
...
`

[PATCH] /v1/tasks/:taskId

Now the PATCH request is the one used to update partially a resource in an API REST.

src/v1/tasks.js

`javascript
...
.patch((req, res, next) => {
for (let prop in req.body) {
tasks[req.params.taskId][prop] = req.body[prop]
}

return res.json(tasks[req.params.taskId])
Enter fullscreen mode Exit fullscreen mode

})
...
`

Let's try this one with curl:
`bash
curl -X PATCH -H "Content-Type: application/json" --data '{"isDone": true}' localhost:3000/v1/tasks/0
{
"isDone" : true,
"description" : "Another task",
"createdAt" : 1481998868351
}
`

Oh yeah!

[DELETE] /v1/tasks/:taskId

I think there's no much to explain here, is pretty much the same as the DELETE for the whole collections instead that this one only deletes one resource.

src/v1/tasks.js

`javascript
...
.delete((req, res, next) => {
tasks.splice(req.params.taskId, 1)

res.status(204).end()
Enter fullscreen mode Exit fullscreen mode

})
...
`

End of the route

Well, I think that with this the awesome Another TODO API is completely functional, maybe I should care about that the info isn't being stored anywhere and every time the server stops I lose it all, but that'll be another time!

Comment and check the code on GitHub!

SurveyJS custom survey software

JavaScript UI Libraries for Surveys and Forms

SurveyJS lets you build a JSON-based form management system that integrates with any backend, giving you full control over your data and no user limits. Includes support for custom question types, skip logic, integrated CCS editor, PDF export, real-time analytics & more.

Learn more

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay