This project was created for a workshop on web app development with the MEAN stack. It uses MongoDB, Express, AngularJS, and Node.js, as well as (...other packages...) to create a basic application with (...features...).
- Getting Started
- Basic Server
- Basic HTML
- Basic Angular App
- Basic Model
- Basic Route
- Note Creation
- Restructure for Scalability
- Miscellaneous Improvements
- Presentation!
Create project directory and create README:
$ mkdir scratch
$ cd scratch
$ touch README.md
Initialize package.json
and install Express:
$ npm init
$ npm install --save express
Note: For the "entry point" prompt during the npm init
process, we are using server.js
(we will create the file in step 2. The Server).
Initialize git repo and create .gitignore
file:
$ git init
$ touch .gitignore
// .gitignore
node_modules
npm-debug.log
Note: Some .gitignore
examples can be found here.
Initial commit:
$ git add .
$ git commit -m "Initial commit"
Create server.js
with the basics:
// Basic Express app
var express = require('express');
var app = express();
// Basic configuration
var port = process.env.PORT || 3001;
// Basic route
app.get('/', function(req, res) {
res.send('Hello, World!');
});
// Start app
app.listen(port, function() {
console.log('Server running on port ' + port);
});
Start the server:
$ node server.js
Note: Using something like node-supervisor prevents having to stop and restart the server every time changes are made.
Go to http://localhost:3001 in the browser, and you should see "Hello, World!"
Add route to server.js
to serve index.html
:
app.get('*', function(req, res) {
res.sendfile('./public/index.html');
});
Create public/index.html
:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Scratch</title>
</head>
<body>
Hello (again), world!
</body>
</html>
Go to http://localhost:3001/anything in the browser, and you should now see "Hello (again), World!"
Note: Unless the initial route created in server.js
is removed, http://localhost:3001 will continue to show "Hello, World!" instead of the contents of index.html
.
Note: At this point, we also added a Bootstrap theme from Bootswatch and a little bit of HTML, so we would have something nicer to look at as we build.
Create public/app/app.js
:
angular
.module('scratchApp', [])
.controller('appController', appController);
function appController() {
var vm = this;
console.log('Why, hello (world)!');
}
Add ng-app
and ng-controller
to index.html
:
<html lang="en" ng-app="scratchApp">
<head>...</head>
<body ng-controller="appController as app">
...
</body>
</html>
Install Mongoose:
$ npm install --save mongoose
Note: Mongoose is an object-document mapper (like an ORM but for a document database) that makes working with MongoDB a lot nicer/easier.
Configure database in server.js
:
var databaseUrl = 'mongodb://localhost/scratch-dev';
mongoose.connect(databaseUrl);
Create notes
model in server.js
:
mongoose.model('Note', {
title: String,
content: String,
});
Ensure that MongoDB is running:
$ mongod
Note: You may have to sudo mongod
; for more on managing MongoDB processes, see here.
Add route to get notes in server.js
:
app.route('/api/notes')
.get(function (req, res) {
Note.find(function(err, notes) {
if (err) res.send(err);
res.json(notes);
});
});
Navigating to http://localhost:3001/api/notes
should show []
, which is the (currently empty) array of notes being returned.
Install body-parser
to parse the body from POST requests:
$ npm install --save body-parser
Configure body-parser
in server.js
:
var bodyParser = require('body-parser');
// ...
app.use(bodyParser.urlencoded({'extended':'true'}));
app.use(bodyParser.json());
app.use(bodyParser.json({ type: 'application/vnd.api+json' }));
Add route for creating notes in server.js
:
app
.route('/api/notes')
// ...
.post(function (req, res) {
var note = {
title: req.body.title,
content: req.body.content,
};
Note.create(note, function(err, note) {
if (err) res.send(err);
res.json(note);
});
});
Add function for submitting form and creating notes in app.js
:
function appController($http) {
// ...
vm.createNote = createNote;
// ...
function createNote(note) {
$http.post('/api/notes', note).then(function(res) {
// Add new note to notes in the view
var note = res.data;
vm.notes.push(note);
}, function(err) {
console.log('Error creating note: ', err.statusText);
});
}
}
Add function for getting notes for display in app.js
:
function appController($http) {
// ...
vm.getNotes = getNotes;
// ...
function getNotes() {
$http.get('/api/notes').then(function(res) {
vm.notes = res.data;
}, function(err) {
console.log('Error retrieving notes: ', err.statusText);
});
}
}
Add HTML form for creating notes in index.html
:
<form>
<label for="title">Title</label>
<input type="text" id="title" ng-model="note.title" placeholder="Note Title">
<label for="content">Content</label>
<textarea id="content" ng-model="note.content" placeholder="Note content..."></textarea>
<button type="submit" class="btn btn-success" ng-click="app.createNote(note)">Create Note</button>
</form>
Add HTML for displaying the (newly-creatable) notes in index.html
:
<div class="panel panel-default" ng-repeat="note in app.notes">
<div class="panel-heading">
{{ note.title }}
</div>
<div class="panel-body">
{{ note.content }}
</div>
</div>
At this point, although the application works, all backend code is in server.js
, all frontend code is in app.js
, and all HTML is in index.html
-- this will not be maintainable or scalable as any more is added to the application.
Frontend changes:
- Create domain-specific directories (e.g.,
/public/app/notes
) - Move domain-specific code to its respective directories (e.g.,
/public/app/notes/notes.controller.js
) - Move HTTP calls from controller(s) into service(s)
- Set up routes (with
ui-router
) and templates - Ensure that all frontend modules and templates are imported and utilized correctly
Backend changes:
- Create a directory to organize API route behaviors (e.g.,
/app/api
) and move existing function definitions into individual files (e.g.,/app/api/notes.js
) - Create a directory to organize models (e.g.,
/app/models
) and move existing model definitions into individual files (e.g.,/app/models/note.js
) - Create a file for defining all of the application's routes (e.g.,
/app/routes.js
) - Create a directory to organize app configuration (e.g.,
/config
) and move existing configurations into individual files (e.g.,/config/db.js
) - Ensure that all modules are imported and utilized correctly
- Note deletion
- Add
method-override
package forPUT
andDELETE
requests (for updating and deleting objects, respectively) - API route to delete notes
- Notes service function to make request
- Notes controller function to call service
- Button in view
- Add
- Note creation
- Add custom filter to reverse note order in list (order newest to oldest)
- Clear form when note is created
- Clear form with "Cancel" button
You can view the PDF for the workshop presentation here.