CRUD Router - tdkehoe/blog GitHub Wiki
The CRUD app requires the following files:
app.js (server)
router.js
templates/directory/index.html
templates/directory/new.html
templates/directory/edit.html
templates/directory/show.html
public/style.css
The following Node modules must be installed. The --save flag adds the modules to the package.json, enabling you to give your app to someone else.
How do you install package.json?
npm install i40 --save
npm install monk --save
npm install qs --save
npm install mustache --save
npm install mime --save
The same files plus fs must be required in router.js. (fs doesn't need to be installed because it's part of Node.js.)
var fs = require('fs'),
var routes = require('i40')(), (note second set of parentheses)
var db = require('monk')('localhost/music'), (note second argument, and that it's not monk = require('monk') )
var qs = require('qs'),
var mustache = require('mustache'), (or view = require('mustache'),)
var mime = require('mime');
Set two more variables:
var bands = db.get('bands'), (get is a method of monk)
The router must also have
module.exports = routes;
That can go at the top or the bottom of the file.
##View Refactor
The view refactor refactors two lines to one line, and simplifies some typing. Use these modules:
var routes = require('routes')(),
fs = require('fs'),
db = require('monk')('localhost/music'),
songs = db.get('songs'),
qs = require('qs'),
view = require('./view.js'); // Changed
mime = require('mime');
Install the same modules, including mustache.
Add a new file view.js:
var fs = require('fs'),
mustache = require('mustache');
var view = {
render: function (path, data) {
var file = fs.readFileSync('templates/' + path + '.html');
return mustache.render(file.toString(), data);
}
};
module.exports = view;
#HTML Pages The index.html page has no links except to routes. The router has the route /public/* so anything in the /public/ directory can be linked, including /public/style.css. Remember to include the leading /. Without the leading / it'll try to find files like templates/bands/templates/bands/index.html.
##Mustache
Mustache merges a template and an object from the database and then renders a webpage. This is the model-view-controller system. The router is the controller, the data is the model, and template is the view.
Tags are enclosed in double curly-braces (mustaches). Tags are executed once, on one object from the database.
A section or block starts with a # and ends with a /. Sections are executed for every object in the array (many items from the database).
##Index page## On the index.html page make two forms, each with one input. Both inputs are type=submit.
One input has the value Update, with its form action= going to _'/bands/{{id}/edit' and method="GET". (Instead of setting the value you can write something between the HTML tags.) All form actions must be routes.
GET and POST can be upper or lower case but the convention is always upper case.
The other input has a value of Delete, with its form action= going to _'/bands/{{id}}/delete' and method="POST".
On the index.html page use these mustache tags to symbolize database fields:
{{_id}} // not :id
{{name}}
{{length}}
Make a mustache section for bands:
{{#bands}
...
{{/bands}}
##New and Edit Pages The new.html and edit.html pages are similar. Each has a single form with a single input. The forms go to respectively '/bands/new' and _'/bands/{{id}}'. The inputs are the same except for the labels, respectively, Submit and Update. Both use POST.
#Router Make the router in router.js. Routes include:
'/bands'
'/bands/new'
'/bands/:id/show'
'/bands/:id/edit'
'/bands/:id/delete'
'/public/*'
Make each route using
routes.addRoute('route', function (req, res, url {...})
##Content Type Each route should include
res.setHeader('Content-Type', _content-type_);
Content type is usually 'text.html' but the /public/* route has dynamic content type: mime.lookup(req.url).
##Request Method
Each route should specify if it uses GET or POST or both.
'/bands' uses GET and '/bands/new' uses GET and POST.
if (req.method === 'GET') {
or
if (req.method === 'POST') {
'/bands/:id/edit' should use UPDATE and '/bands/:id/delete' should use DELETE but these aren't HTML request methods. Instead we have to make work-arounds. '/bands/:id/edit' uses GET and '/bands/:id/delete' uses POST.
##Index route
The index route goes to the index.html page, which displays all the records in the database collection.
Using the unabstracted code:
bands.find({}, function(err, docs) {
if (err) {throw err; }
else {
var file = fs.readFileSync('templates/songs/index.html');
var template = view.render(file.toString(), {bands: docs});
res.end(template);
The first line accesses the database and finds all records. The callback function reads the HTML file. The HTML file is converted to a string, then appends the object from the database, and the two are combined and rendered to produce a template or webpage. Finally the rendered template is the end of the response.
Using the abstracted code:
songs.find({}, function(err, docs) {
if (err) {throw err; }
else {
var template = view.render('songs/index', {songs: docs});
res.end(template);
A single line of code reads in the webpage, collects the database object, and renders the template.
##New route
The new route goes to the new page, which enables users to add a new item to the database has a GET request (to display the form page) and a POST request (to send the new data to the database).
Using the unabstracted code:
if (req.method === 'GET') {
var file = fs.readFileSync('templates/bands/new.html');
var template = view.render(file.toString(), {});
res.end(template);
Using the abstracted code:
if (req.method === 'GET') {
var template = view.render('bands/new', {});
res.end(template);
The POST request begins with creating an empty string, then runs an asynchronous accumulator to build the data string.
if (req.method === 'POST') {
var data = '';
req.on('data', function(chunk){
data += chunk;
});
req.on('end', function(){
var band = qs.parse(data);
bands.insert(band, function(err, doc) { // doc is never used
if (err) {res.end('error'); }
else {
res.writeHead(302, {'Location': '/bands'});
res.end();
}
});
});
}
When the data string is finished the data is parsed and then inserted into the database. Multiple fields can be inserted without specifying the fields (e.g., song name and song length).
##Delete route
The delete route deletes items from the database collection. It redirects to the index page (not to a delete.html page).
routes.addRoute('/bands/:id/delete', function (req, res, url) {
if (req.method === 'POST') {
bands.remove({_id: url.params.id}, function(err, doc) {
if (err) {throw err; }
else {
res.writeHead(302, {'Location': '/bands'});
res.end();
}
});
}
});
Note that the delete route has a dynamic segment: :id.
The delete route makes a POST request.
It accesses the database with the remove method.
The delete route ends with a redirect to the index page.
##Show route
The show route goes to the show page, which displays everything about a single record in the database collection. It uses a dynamic segment and findOne to access the database collection.
Using the unabstracted code:
routes.addRoute('/songs/:id/show', function (req, res, url) {
res.setHeader('Content-Type', 'text/html');
if (req.method === 'GET') {
songs.findOne({_id: url.params.id}, function(err, doc) {
if (err) {throw err; }
else {
var file = fs.readFileSync('templates/songs/show.html');
var template = view.render(file.toString(), {songs: doc}); // doc (singular)
res.end(template);
}
});
}
});
Using the abstracted code:
routes.addRoute('/songs/:id/show', function (req, res, url) {
res.setHeader('Content-Type', 'text/html');
if (req.method === 'GET') {
songs.findOne({_id: url.params.id}, function(err, doc) {
if (err) {throw err; }
else {
var template = view.render('songs/show', {songs: doc});
res.end(template);
}
});
}
});
##Public (splat) route
This route hooks up style.css, etc. No GET or POST request is needed.
routes.addRoute('/public/*', function (req, res, url) {
res.setHeader('Content-Type', mime.lookup(req.url));
fs.readFile('./' + req.url, function (err, file) {
if (err) {
res.setHeader('Content-Type', 'text/html');
res.end('404');
}
else {
res.end(file);
}
});
});
#Run Your App# To run the app, from the home directory, in different tabs:
npm start (or npm run start?)
mongod
Open browser to specified port.
There's no need to do anything else with the database. If you want to look at database, open another tab in the terminal and enter mongo. You can then run commands such as
show dbs
use music
db.bands.find()