Introduce the fundamentals of the mean stack
Clone the project repo: https://github.com/JameelB/node-todo
git clone https://github.com/JameelB/node-todo
or
git clone git@github.com:JameelB/node-todo.git
Create the following folders and files as listed below:
- public <!-- Contains all the files for the frontend angular application -->
> core.js <!-- Contains all of the angular code for the app -->
> index.html <!-- Main view -->
- package.json <!-- Contains the NPM configuration to install dependencies/modules -->
- server.js <!-- Node configuration -->
This is a simplified file structure for this tutorial. In larger application, this would be broken down further to separate duties between client and server. Checkout MEAN.io which is a good boilerplate to see best practices and how to separate file structure.
To see expected results for this step checkout the branch step-01: git checkout step-01
The package.json holds configuration for the app. Node's package manager (NPM) will use this to install any dependencies or modules that are specified here.
Copy the following below to your package.json:
{
"name" : "node-todo",
"version" : "0.0.1",
"description" : "Simple todo application.",
"main" : "server.js",
"author" : "ScotchIO",
"dependencies" : {
"express" : "~4.7.2",
"mongoose" : "~3.6.2",
"morgan" : "~1.2.2",
"body-parser": "~1.5.2",
"method-override": "~2.1.2"
}
}
Save the file and run npm install
. This will install the dependencies specified in your package.json
To see expected results for this step checkout the branch step-02: git checkout step-02
In the package.json
file, it specified that the main file would be server.js
. This is the main
file for our Node app and where we will configure the entire application.
In this file the following configuration needs to be done: - Database connection - Mongoose models creation - RESTful API routes definition - Frontend Angular routes definition - Set a port for the app to listen to.
// server.js
// set up ========================
var express = require('express');
var app = express(); // create our app w/ express
var mongoose = require('mongoose'); // mongoose for mongodb
var morgan = require('morgan'); // log requests to the console (express4)
var bodyParser = require('body-parser'); // pull information from HTML POST (express4)
var methodOverride = require('method-override'); // simulate DELETE and PUT (express4)
// configuration =================
var localDbUrl = 'mongodb://localhost/meanstacktutorial'; //set your local database URL
mongoose.connect(localDbUrl); //Connects to your local database.
app.use(express.static(__dirname + '/public')); // set the static files location /public/img will be /img for users
app.use(morgan('dev')); // log every request to the console
app.use(bodyParser.urlencoded({'extended':'true'})); // parse application/x-www-form-urlencoded
app.use(bodyParser.json()); // parse application/json
app.use(bodyParser.json({ type: 'application/vnd.api+json' })); // parse application/vnd.api+json as json
app.use(methodOverride());
// listen (start app with node server.js) ======================================
app.listen(8080);
console.log("App listening on port 8080");
Run npm run start
and you should see App listening on port 8080
logged out in your terminal.
If you go to your browser and go to localhost:8080/
, it should not give any errors and show an empty page.
Install nodemon globally by running npm install -g nodemon
. Run your application with nodemon server.js
.
By doing this, it will automatically monitor for file changes and restart the server.
To see expected results for this step checkout the branch step-03: git checkout step-03
Configure the model and routes for the REST API
First, we need to define a model for our data that has a text field of type string.
MongoDB will automatically generate the _id
field for each record created.
Copy this just before the app.listen
in your server.js
var Todo = mongoose.model('Todo', {
text : String
});
Next, we need to generate our Express routes to handle our API calls. Add this to your server.js
file
just after the model definition above. This will make our application's API accessible from /api/todos.
app.get('/api/todos', function(req, res) {
// use mongoose to get all todos in the database
Todo.find(function(err, todos) {
// if there is an error retrieving, send the error. nothing after res.send(err) will execute
if (err)
res.send(err)
res.json(todos); // return all todos in JSON format
});
});
// create todo and send back all todos after creation
app.post('/api/todos', function(req, res) {
// create a todo, information comes from AJAX request from Angular
Todo.create({
text : req.body.text,
done : false
}, function(err, todo) {
if (err)
res.send(err);
// get and return all the todos after you create another
Todo.find(function(err, todos) {
if (err)
res.send(err)
res.json(todos);
});
});
});
// delete a todo
app.delete('/api/todos/:todo_id', function(req, res) {
Todo.remove({
_id : req.params.todo_id
}, function(err, todo) {
if (err)
res.send(err);
// get and return all the todos after you create another
Todo.find(function(err, todos) {
if (err)
res.send(err)
res.json(todos);
});
});
});
HTTP Verb | URL | DESCRIPTION |
---|---|---|
GET | /api/todos | Get all of the todos |
POST | /api/todos | Create a single todo |
DELETE | /api/todos/:todo_id | Delete a single todo |
To see expected results for this step checkout the branch step-04: git checkout step-04
Add the following route to the server.js
file just before the app.listen
and after the API routes.
app.get('*', function(req, res) {
res.sendfile('./public/index.html');
});
This will load this view and angular will handle the changes on the frontend.
Add the following to your public/core.js
file
- Create an angular module.
var toDoApp = angular.module('todo-app', []);
function mainController($scope, $http) {
$scope.formData = {};
// when landing on the page, get all todos and show them
$http.get('/api/todos')
.success(function(data) {
$scope.todos = data;
console.log(data);
})
.error(function(data) {
console.log('Error: ' + data);
});
// when submitting the add form, send the text to the node API
$scope.createTodo = function() {
$http.post('/api/todos', $scope.formData)
.success(function(data) {
$scope.formData = {}; // clear the form so our user is ready to enter another
$scope.todos = data;
console.log(data);
})
.error(function(data) {
console.log('Error: ' + data);
});
};
// delete a todo after checking it
$scope.deleteTodo = function(id) {
$http.delete('/api/todos/' + id)
.success(function(data) {
$scope.todos = data;
console.log(data);
})
.error(function(data) {
console.log('Error: ' + data);
});
};
}
In your public/index.html
file, add the following below:
<!doctype html>
<!-- ASSIGN OUR ANGULAR MODULE -->
<html ng-app="scotchTodo">
<head>
<!-- META -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"><!-- Optimize mobile viewport -->
<title>Node/Angular Todo App</title>
<!-- SCROLLS -->
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css"><!-- load bootstrap -->
<style>
html { overflow-y:scroll; }
body { padding-top:50px; }
#todo-list { margin-bottom:30px; }
</style>
<!-- SPELLS -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script><!-- load jquery -->
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular.min.js"></script><!-- load angular -->
<script src="core.js"></script>
</head>
<!-- SET THE CONTROLLER AND GET ALL TODOS -->
<body ng-controller="mainController">
<div class="container">
<!-- HEADER AND TODO COUNT -->
<div class="jumbotron text-center">
<h1>I'm a Todo-aholic <span class="label label-info">{{ todos.length }}</span></h1>
</div>
<!-- TODO LIST -->
<div id="todo-list" class="row">
<div class="col-sm-4 col-sm-offset-4">
<!-- LOOP OVER THE TODOS IN $scope.todos -->
<div class="checkbox" ng-repeat="todo in todos">
<label>
<input type="checkbox" ng-click="deleteTodo(todo._id)"> {{ todo.text }}
</label>
</div>
</div>
</div>
<!-- FORM TO CREATE TODOS -->
<div id="todo-form" class="row">
<div class="col-sm-8 col-sm-offset-2 text-center">
<form>
<div class="form-group">
<!-- BIND THIS VALUE TO formData.text IN ANGULAR -->
<input type="text" class="form-control input-lg text-center" placeholder="I want to buy a puppy that will love me forever" ng-model="formData.text">
</div>
<!-- createToDo() WILL CREATE NEW TODOS -->
<button type="submit" class="btn btn-primary btn-lg" ng-click="createTodo()">Add</button>
</form>
</div>
</div>
</div>
</body>
</html>
To see expected results for this step checkout the branch step-05: git checkout step-05
A set of exercises on building a simple todo web application using MongoDB, Express, AngularJS and Node.js.
The web application should allow for CRUDL operations of a dataset in the store.
Based on tutorial from scotch-io : https://scotch.io/tutorials/creating-a-single-page-todo-app-with-node-and-angular
Follow up tutorial: https://scotch.io/bar-talk/setting-up-a-mean-stack-single-page-application
Working demo on glitch: https://glitch.com/edit/#!/blossom-blinker