I know, I know… I said I would post more often. In my defense, I moved to another city and it’s exhausting. However, I’m here now and my gift to you before the holidays is finishing up our node.js backend. Let’s dive in.
We’re going to expand our application so that it provides the same RESTful HTTP API as a json-server.
We’re not going to deeply examine Fielding's definition of REST or spend time pondering about what is and isn't RESTful. Instead, we’ll take a simplified view and concern ourselves with how RESTful APIs are understood in web applications.
One convention is to create the unique address for resources by combining the name of the resource type with the resource's unique identifier.
Let's assume that the root URL of our service is example.com/api
.
If we define the resource type of person
to be people
, then the address of a person resource with the identifier 10, has the unique address example.com/api/people/10
.
The URL for the entire collection of all note resources is example.com/api/people
.
We can execute different operations on resources. The operation to be executed is defined by the HTTP
verb:
URL | verb | functionality |
---|---|---|
people/10 | GET | fetches a single resource |
people | GET | fetches all resources in the collection |
people | POST | creates a new resource based on the request data |
people/10 | DELETE | removes the identified resource |
people/10 | PUT | replaces the entire identified resource with the request data |
people/10 | PATCH | replaces a part of the identified resource with the request data |
Fetching a Single Resource
I know I’ve been rambling about REST for a while, but I swear I wouldn’t do it if it weren’t important. Let’s circle back to our backend application and fetch a single resource.
app.get('/api/people/:id', (request, response) => {
const id = Number(request.params.id)
const person = people.find(p => p.id === id)
if (person) {
response.json(person)
} else {
response.status(404).end()
}
})
Lots to unpack here, so I’ll try to break it down.
-
app.get('/api/people/:id', ...)
will handle all HTTPGET
requests that are of the form/api/notes/SOMETHING
, where SOMETHING is an arbitrary string. - The
id
parameter in the route of the request is accessed through the request object. - The Number constructor wraps our
id
parameter and turns it into an integer (This is just in case ourid
turns out to be a string, we’re thinking preemptively here). - Our
if
block leverages the fact that all JavaScript objects are truthy, meaning that it will evaluate to true in a comparison operation. However, undefined is falsy meaning that our block will evaluate to false. Thus sending an error status code if no person is found.
Delete a Resource
Deletion happens by making an HTTP DELETE
request to the url of the resource.
Since we just retrieved a single resource, this should be easy.
app.delete('/api/people/:id', (request, response) => {
const id = Number(request.params.id)
people = people.filter(p => p.id !== id)
response.status(204).end()
})
Once deletion of the resource is successful, meaning that the person exists and it is removed, we can respond to the request with the status code 204 no content and return no data with the response.
POST and Receiving Data
Ok, let's make it possible to add new people to the server. Adding a person happens by making an HTTP POST
request to the address localhost:3001/api/people
, and by sending all the information for the new person in the request body in the JSON
format.
To access the data easily, we’ll need the help of the express json-parser that is taken to use with command app.use(express.json())
.
app.use(express.json())
//...
app.post('/api/people', (request, response) => {
const person = request.body
console.log(person)
response.json(person)
})
Here, the event handler function can access the data from the body property of the request object.
Without the
json-parser
, the body property would be undefined. Thejson-parser
functions so that it takes theJSON
data of a request, transforms it into a JavaScript object and then attaches it to the body property of the request object before the route handler is called.
For the time being, the application does not do anything with the received data besides printing it to the console and sending it back in the response.
Let's return to the application and finalize the handling of the request!
const generateId = () => {
const maxId = people.length > 0
? Math.max(...people.map(p => p.id))
: 0
return maxId + 1
}
app.post('/api/people', (request, response) => {
const body = request.body
if (!body.name) {
return response.status(400).json({
error: 'name missing'
})
}
const person = {
name: body.name,
number: body.number,
id: generateId(),
}
people = people.concat(person)
response.json(person)
})
We need a unique id for each new person so we find out the largest id number in the current list and assign it to the
maxId
variable. The id of the new person is then defined asmaxId + 1
.If the received data is missing a value for the name property, the server will respond to the request with the status code 400 bad request
Calling
return
is crucial, otherwise the code will execute to the very end and a malformed person object gets saved to the application!
Here's how our backend looks now:
const express = require('express')
const app = express()
app.use(express.json())
let people = [
{
name: "Hannah Rickard",
number: "06-51-99-56-83",
id: 1
},
{
name: "Hyun Namkoong",
number: "10987654",
id: 2
},
{
name: "Courtney Martinez",
number: "3691215",
id: 3
}
]
app.get('/', (request, response) => {
response.send('<h1>Phonebook</h1>')
})
app.get('/api/people', (request, response) => {
response.json(people)
})
app.get('/api/people/:id', (request, response) => {
const id = Number(request.params.id)
const person = people.find(p => p.id === id)
if (person) {
response.json(person)
} else {
response.status(404).end()
}
})
app.delete('/api/people/:id', (request, response) => {
const id = Number(request.params.id)
people = people.filter(p => p.id !== id)
response.status(204).end()
})
const generateId = () => {
const maxId = people.length > 0
? Math.max(...people.map(p => p.id))
: 0
return maxId + 1
}
app.post('/api/people', (request, response) => {
const body = request.body
if (!body.name) {
return response.status(400).json({
error: 'name missing'
})
}
const person = {
name: body.name,
number: body.number,
id: generateId(),
}
people = people.concat(person)
response.json(person)
})
const PORT = 3001
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`)
})
Aaaand we're done! We've managed to GET
a single resource with the id
. With that same logic, you were able to DELETE
a resource. Finally, we received and persisted data with POST
.
As we approach the holidays and the end of the year, I want to wish you all the best, and please, if you go shopping, return your shopping cart to its designated spot.
Resources
JSON Server
REST
Number Constructor
HTTP Status Codes
JSON Parser
Top comments (3)
Nice Post it Maloney 😀 Fellow photographer? ❤️
And yes! analog or bust!
lol my terrible attempt at humor