CRUD in Express
CRUD is an acronym that stands for Create, Read, Update, Destroy. These are the basic operations that you can perform on data. Most sites you interact with on the internet are CRUD sites. Almost everything you do on the web is a CRUD action: Creating a user (create), Listing comments (read) to editing your profile (update), to deleting a video you uploaded on youtube (destroy).
RESTful Routing
On the web the best practice for CRUD actions uses something called RESTful routing. The basic idea is your route (URL) should relate to the type of item you are interacting with, as well as the action you'll be performing to change/view the state of the item(s).
So if you were CRUDing Animals the route would be /animals
and the following routes would form a full set of CRUD RESTful routes
VERB | URL | Action (CRUD) | Description |
---|---|---|---|
GET | /animals | Index (Read) | lists all animals |
GET | /animals/1 | Show (Read) | list information about a specific animal (id = 1) |
POST | /animals | Create | creates an animal with the POST payload data |
PUT | /animals/1 | Update | updates the data for a specific animal (id = 1) |
DELETE | /animals/1 | Delete (Destroy) | deletes the animal with the specified id (1) |
CRUD in action
For now we're just going to focus on POST and GET (Create and Read)
Backend data store
For this example, we'll be using a JSON object as our data store. In the root of the project, create a JSON file with the following contents:
[
{
"name":"Neko",
"type":"cat"
},
{
"name":"Fido",
"type":"dog"
},
{
"name":"Mufasa",
"type":"lion"
},
{
"name":"Tony",
"type":"tiger"
},
{
"name":"Kuma",
"type":"bear"
}
]
Index / Read (GET)
Index is a route (URL) that lists all items of a specific type. It is a GET request to (in this example) /animals
.
Index route -- in index.js
// Include fs (short for filesystem) at the top. No need to install via npm
const fs = require('fs')
// Express index route for animals (lists all animals)
app.get('/animals', function(req, res) {
let animals = fs.readFileSync('./data.json')
animals = JSON.parse(animals)
res.send('animals/index', {myAnimals: animals})
})
In the above example we load the animals/index.ejs
view and pass it animals
as myAnimals
. This means in the index.ejs file we can access myAnimals directly.
Index view -- in /views/animals/index.handlebars
<ul>
{{#each myAnimals}}
<li>{{this.name}} is a {{this.type}}</li>
{{/each}}
</ul>
Here we are looping through the myAnimals
array passed from the route and
displaying a list of all of the items.
output would be something like this:
- Neko is a cat
- Fido is a dog
- Mufasa is a lion
- Tony is a tiger
- Kuma is a bear
Show / Read (GET)
Show is a route that displays a single item of a specific type. It is GET
request to (in this example) /animals/1
Show route -- in index.js
//express index route for animals (lists all animals)
app.get('/animals/:idx', function(req, res) {
// get animals
let animals = fs.readFileSync('./data.json');
animals = JSON.parse(animals);
//get array index from url parameter
let animalIndex = parseInt(req.params.idx)
//render page with data of the specified animal
res.render('animals/show', {myAnimal: animals[animalIndex]})
});
In the above example we load the animals/show.handlebars
view and pass it a specific
item from the animals
array as myAnimal
. We use the :idx url parameter to
specify which animal to display. Again, this means in the show.ejs file we can
access myAnimal directly.
{{ myAnimal.name }} is a {{ myAnimal.type }}.
Display the animal based on the url. With the example /animals/1
we would be
displaying the 2nd item in the array (index starting at 0) and get output like
this...
Fido is a dog.
Create (POST)
To create an item (animal in this example) we need to make a POST to the url /animals
with the data about that animal. We do this using a form with the method attribute set to post
and the action set to the create url.
frontend - form html
<form method="POST" action="/animals">
<label for="animalType">Type</label>
<input type="text" name="type">
<label for="animalName">Name</label>
<input id="animalName" type="text" name="name">
<input id="animalType" type="submit">
</form>
When the above form is submitted it will make a POST
to the url /animals
with the data contained in the form fields.
To receive this data we need to create a POST
route in express and use the body-parser
npm module to receive that data. But first, let's set up the body-parser
module!
BodyParser
Parsing parameters from a form needs an external module called body-parser
.
yarn add body-parser
index.js
const express = require('express')
const bodyParser = require('body-parser')
const app = express()
// tell your app to use the module
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({
extended: true
}))
Note that we set an attribute extended
to true
when telling our app to use the body parser. This attribute determines which library is used to parse data. Discussion on extended here.
Now, if we try to add this backend route, calling req.body
should contain the form input.
backend - express route
app.post('/animals', function(req, res) {
//debug code (output request body)
console.log(req.body);
res.send(req.body);
});
Form data is passed as payload of the request. Every field that has a name will
be included in that payload and it is sent as form encoded text. When
body-parser
is installed it automatically parses the form body into a
javascript object that we can use and it stores it in req.body
so we can use it. All of this is done as middleware, which we just configured.
In the above example we could access the animal type form field by using
req.body.type
and the name field by using req.body.name
. This correlates
directly to the names given to the form fields in the form html above.
Generally, the code in the express route would contain code that would CREATE an item in a database and redirect the user to a route with a confirmation message of some sort or just back to the index route. For this example we're going to use the JSON file created above to store our data. This will involve three steps:
- Reading the JSON file
- Pushing the new animal to the object
- Writing the new JSON file
app.post('/animals', function(req, res) {
// read animals file
let animals = fs.readFileSync('./data.json');
animals = JSON.parse(animals);
// add item to animals array
animals.push(req.body);
// save animals to the data.json file
fs.writeFileSync('./data.json', JSON.stringify(animals));
//redirect to the GET /animals route (index)
res.redirect('/animals');
});
Additional: Show / Read (GET) with a Form
There may be instances where you need to GET animals, but you don't want them all. A good example is filtering animals with a specific name via a search bar.
In these cases, you don't want to use a POST action, because POST is reserved for creating new resources. Instead, we can create another form with a GET method, and read the data via a querystring.
Animals index page
<form method="GET" action="/animals">
<label for="nameFilter">Filter by Name</label>
<input id="nameFilter" type="text" name="nameFilter">
<input type="submit">
</form>
Note that this form will submit to the same page. We'll need to see if there's a querystring, then filter the animals if one is present.
app.get('/animals', function(req, res){
let animals = fs.readFileSync('./data.json');
animals = JSON.parse(animals);
let nameFilter = req.query.nameFilter;
if (nameFilter) {
animals = animals.filter(function(animal) {
return animal.name.toLowerCase() === nameFilter.toLowerCase();
});
}
res.render('animals/index', {myAnimals: animals});
});
Forms
So far we've learnt to send actions using HTTP Methods, PUT for an update and DELETE for a destroy action. There is a problem however. Browsers currently only support GET and POST, so whenever we send a form it will actually be sent using POST. To overcome this we need to use the Method Override Package. This will allow us to hide some information in the form that when received and processed in Express will change the method to the correct one. Here's an example
var express = require('express')
var methodOverride = require('method-override')
var app = express()
// override with POST having ?_method=DELETE
app.use(methodOverride('_method'))
Example call with query override using HTML
<form method="POST" action="/resource?_method=DELETE">
<button type="submit">Delete resource</button>
</form>
Controllers
We have been placing all routes into index.js
when creating a Node/Express app, but this can get cumbersome when dealing with many routes. The solution is to separate routes into separate files and attach the routes to the Express router.
index.js
const animalsCtrl = require("./controllers/animals")
app.use("/animals", animalsCtrl);
controllers/animals.js
const express = require("express");
const router = express.Router();
router.get("/", function(req, res) {
});
module.exports = router;
Note that the routes should be defined relative to the definition in app.use
. For example, the route defined above in controllers/animals.js
will be http://localhost:3000/animals
.