of 24
03/04/14 Pixelhandler's Blog
pixelhandler.com/posts/develop-a-restful-api-using-nodejs-with-express-and-mongoose 1/24
Pixelhandler's
Blog
Pushin' & pullin' pixels on the web
Develop a RESTful API Using Node.js With Express and Mongoose
by pixelhandler (2 years ago)
For the past couple months I've been developing with Backbone.js and mocking data for an application. I've worked in the
ecommerce industry for a few years and thought it would be a good idea to create a serious of posts on the topic of developing
with Backbone using an example with some complexity, perhaps more than a 'todos' or 'blog' application, so the example will
utilize a familiar Web application, an online store. To program a data-driven asynchronous application using a language I
already know, JavaScript, the best way to learn is to write some code. So, I researched a few example applications using Node.js
with a MongoDB database. This article is intended to be the first in a series on the topic building an online store using REST and
Backbone.js to structure the code. This tutorial is not intended for production code, but rather an exploration of developing
interactions with a RESTful API. This first post lays down a foundation for developing with a local API, then I can get into using
the application with Backbone; but let's get into the server-side for a bit first.
API Design for Mock Ecommerce Application
Goals for the Web service:
Simple API design and pragmatic REST Web service, with only 2 base URLs per resource
Keep verbs out of your base URLs
Our HTTP verbs are POST, GET, PUT, and DELETE ([Create, Read, Update, Delete][crud])
Concrete names are better than abstract
Example : two (2) resources ( /products and /products/XX ) and the four (4) HTTP verbs
ResourcePOST
(create)
GET
(read)
PUT
(update)
DELETE
(delete)
/products create a new productlist productsbulk update products delete all products
/products/1234error show 1234 if exists update 1234, else errordelete 1234
Nouns
Products
There a many configurations for setting up a product to sell online, some with no options, with multiple configurable options, or
groups of products. For this example I will use one of my favorite things, a t-shirt. This type of product can be configured with
size and color options, e.g. Black - Large, or Red - Medium; specific material variations would be represented as separate
products, like long- vs. short-sleeves.
Product attributes include:
Id,
Title,
Description,
Images: [ { Kind, URL } ],
Categories: [ { Name } ],
Style: Number,
Varients: [
{
Color,
Images: [
{ Kind, URL }
],
Sizes: [
{ Size, Available, SKU, Price }
],
}
]
03/04/14 Pixelhandler's Blog
pixelhandler.com/posts/develop-a-restful-api-using-nodejs-with-express-and-mongoose 2/24
JSON data may be represented like so:
{
"title": "My Awesome T-shirt",
"description": "All about the details. Of course it's black.",
"images": [
{
"kind": "thumbnail",
"url": "images/products/1234/main.jpg"
}
],
"categories": [
{ "name": "Clothes" },
{ "name": "Shirts" }
],
"style": "1234",
"variants": [
{
"color": "Black",
"images": [
{
"kind": "thumbnail",
"url": "images/products/1234/thumbnail.jpg"
},
{
"kind": "catalog",
"url": "images/products/1234/black.jpg"
}
],
"sizes": [
{
"size": "S",
"available": 10,
"sku": "CAT-1234-Blk-S",
"price": 99.99
},
{
"size": "M",
"available": 7,
"sku": "CAT-1234-Blk-M",
"price": 109.99
}
]
}
],
"catalogs": [
{ "name": "Apparel" }
]
}
The above object has a variety of types composing a document that should bring up some challenges in learning how to store
and update the document in a MongoDB database. The first thing I needed to do was install MongoDB (see the quickstart
guide). To get to know the database I tried out using Mongo in the console.
I'll need to define the Web service urls to interact with the data via REST like so...
/products - list
/products/:id - single
Data: MongoDB using Mongoose with Express framework running on Node.js
For installation of Node.js, NPM, and Express see:
Installing Node.js
npm is a package manager for node.
npm install express
Also there are plenty of links at the end of the article to learn about this stack.
03/04/14 Pixelhandler's Blog
pixelhandler.com/posts/develop-a-restful-api-using-nodejs-with-express-and-mongoose 3/24
Working with data using the Mongoose package in node, I will need to research storing JSON documents. The Mongoose
documentation outlines the use of schema types and embedded documents; so these can be integrated into a product model
to store the product JSON above. Models are defined by passing a Schema instance to mongoose.model.
A Node App Running Express to Access Data using a RESTful Web Service
I found that a section of 'Backbone Fundamentals' has an example application which is built using this same stack : Node.js,
Express, Mongoose and MongoDB. I reviewed the example methods to GET, POST, PUT and DELETE. Then I got started with an
instance of the express.HTTPServer .
I created a file app.js and added the following JavaScript code:
var application_root = __dirname,
express = require("express"),
path = require("path"),
mongoose = require('mongoose');
var app = express.createServer();
// Database
mongoose.connect('mongodb://localhost/ecomm_database');
// Config
app.configure(function () {
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(path.join(application_root, "public")));
app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});
app.get('/api', function (req, res) {
res.send('Ecomm API is running');
});
// Launch server
app.listen(4242);
In the above code, the line beginning with var loads the modules needed for the API, and app = express.createServer() creates the
Web server. The Web server can also serve static files in a public directory, the line in the configure block
app.use(express.static(path.join(application_root, "public"))); sets up the public directory to use static files. The code,
mongoose.connect('mongodb://localhost/ecomm_database'); , hooks up the database. All I needed to do is name the database, in this example I
used the name: 'ecomm_database'. With MongoDB is setup and running, the actual database is automatically generated. To run
mongod , on the command line I needed to execute the command:
mongod run --config /usr/local/Cellar/mongodb/2.0.1-x86_64/mongod.conf
Since I installed MongoGB on OSX Lion, the above command was printed out following the installation on my MacBook.
The code app.listen(4242); sets up the server to respond to the URL: http://localhost:4242. Once mongod is running, to start up the
server genereated with the app.js file... I executed node app.js on the command line.
Now, I have a folder named 'ecomapi' and inside this directory is the 'app.js' file and a directory named 'public' which has an
'index.html' file. With the static index.html file I can load the data using jQuery which I am linking to on a CDN. Later I will be
able to try out AJAX calls to create products using my browser's JavaScript console.
ecomapi
|-- app.js
03/04/14 Pixelhandler's Blog
pixelhandler.com/posts/develop-a-restful-api-using-nodejs-with-express-and-mongoose 4/24
`-- public
`-- index.html
API index
Nouns...
/products
/products/:id
In my browser I can load http://localhost:4242 and see that the static index.html file loads and also hitting
http://localhost:4242/api spits out some text 'Ecomm API is running', which is the result of the get response:
app.get('/api', function (req, res) {
res.send('Ecomm API is running');
});
Up to this point we have a simple server with a single get response, next I can add in the data models and REST services.
Setup a Simple Model Using CRUD (create, read, update, delete)
Following the app.configure code block, in the app.js, I added a Schema and a Product Model:
var Schema = mongoose.Schema;
var Product = new Schema({
title: { type: String, required: true },
description: { type: String, required: true },
style: { type: String, unique: true },
modified: { type: Date, default: Date.now }
});
Since I still needed to learn how to use the schema types and embedded documents that come with mongoose, I didn't add the
price yet which should be set on a combination of color and size.
To use the model I created a variable ProductModel :
var ProductModel = mongoose.model('Product', Product);
Now I can add the CRUD methods:
READ a List of Products
app.get('/api/products', function (req, res){
return ProductModel.find(function (err, products) {
if (!err) {
return res.send(products);
} else {
return console.log(err);
}
});
});
03/04/14 Pixelhandler's Blog
pixelhandler.com/posts/develop-a-restful-api-using-nodejs-with-express-and-mongoose 5/24
CREATE a Single Product
app.post('/api/products', function (req, res){
var product;
console.log("POST: ");
console.log(req.body);
product = new ProductModel({
title: req.body.title,
description: req.body.description,
style: req.body.style,
});
product.save(function (err) {
if (!err) {
return console.log("created");
} else {
return console.log(err);
}
});
return res.send(product);
});
READ a Single Product by ID
app.get('/api/products/:id', function (req, res){
return ProductModel.findById(req.params.id, function (err, product) {
if (!err) {
return res.send(product);
} else {
return console.log(err);
}
});
});
UPDATE a Single Product by ID
app.put('/api/products/:id', function (req, res){
return ProductModel.findById(req.params.id, function (err, product) {
product.title = req.body.title;
product.description = req.body.description;
product.style = req.body.style;
return product.save(function (err) {
if (!err) {
console.log("updated");
} else {
console.log(err);
}
return res.send(product);
});
});
});
DELETE a Single Product by ID
app.delete('/api/products/:id', function (req, res){
return ProductModel.findById(req.params.id, function (err, product) {
return product.remove(function (err) {
if (!err) {
console.log("removed");
return res.send('');
} else {
console.log(err);
}
03/04/14 Pixelhandler's Blog
pixelhandler.com/posts/develop-a-restful-api-using-nodejs-with-express-and-mongoose 6/24
});
});
});
NOTE: To exit your running app.js job, press control-c then re-start your updated app.js using the same command as before: node
app.js
With the new product model and CRUD methods serving up a RESTful service at http://localhost:4242/api I can utilize the
index.html (with jQuery)... and in my browser's console I can fiddle with my new Web service using jQuery's AJAX methods.
Specifically, by loading http://localhost:4242/ and executing commands in the JavaScript console I can using ($.ajax) POST to
create a new product.
jQuery.post("/api/products", {
"title": "My Awesome T-shirt",
"description": "All about the details. Of course it's black.",
"style": "12345"
}, function (data, textStatus, jqXHR) {
console.log("Post resposne:"); console.dir(data); console.log(textStatus); console.dir(jqXHR);
});
The post response is something like:
_id: "4f34d8e7f05ebf212b000004"
description: "All about the details. Of course it's black."
modified: "2012-02-10T08:44:23.372Z"
style: "12345"
title: "My Awesome T-shirt"
The _id property was added automatically, this value can be used to UPDATE, READ, or DELETE the record. Notice all the
console.log() and console.dir() calls I added within the anonymous functions' 'success' callbacks. With the logging in place, I can
inspect the server's response in the console or by viewing the responses on in network tab of my browser's developer tools.
To READ the product data I just created, I execute the following code in my browser's JavaScript console:
jQuery.get("/api/products/", function (data, textStatus, jqXHR) {
console.log("Get resposne:");
console.dir(data);
console.log(textStatus);
console.dir(jqXHR);
});
The above GET request reads all products; to read a specific product add the ID to the URL like so:
jQuery.get("/api/products/4f34d8e7f05ebf212b000004", function(data, textStatus, jqXHR) {
console.log("Get resposne:");
console.dir(data);
console.log(textStatus);
console.dir(jqXHR);
});
To test the UPDATE request use PUT:
jQuery.ajax({
url: "/api/products/4f34d8e7f05ebf212b000004",
type: "PUT",
data: {
"title": "My Awesome T-shirt in Black",
"description": "All about the details. Of course it's black, and long sleeve",
"style": "12345"
03/04/14 Pixelhandler's Blog
pixelhandler.com/posts/develop-a-restful-api-using-nodejs-with-express-and-mongoose 7/24
},
success: function (data, textStatus, jqXHR) {
console.log("Post resposne:");
console.dir(data);
console.log(textStatus);
console.dir(jqXHR);
}
});
The above code is about the same as the the previous code I used to create the product document and store in MongoDB.
However, I appended the product's description with the text: 'black, and long sleeve'.
Now, when I get the product by ID, I see the updated text added to the product description:
jQuery.get("/api/products/4f34d8e7f05ebf212b000004");
Or I can visit : http://localhost:4242/api/products/4f34d8e7f05ebf212b000004 to see the text response only.
I can also DELETE the product:
jQuery.ajax({
url: "/api/products/4f34d8e7f05ebf212b000004",
type: "DELETE",
success: function (data, textStatus, jqXHR) {
console.log("Post resposne:");
console.dir(data);
console.log(textStatus);
console.dir(jqXHR);
}
});
Now when I load http://localhost:4242/api/products/4f34d8e7f05ebf212b000004 the server response with a null response
TIP: I am using a log of console.log() and console.dir() calls within the success (anonymous) functions to view the responses from the
server.
Embedded Documents for the Remaining Product Attributes
I am now adding a few items to the product model: images, categories, catalogs, variants. A t-shirt product may have many
variants with size and color options; the pricing should be configured by the combination of: the selected size option which
belongs to a selected color option. The product may belong one or more product catalogs, and also should have one or more
associated categories.
// Product Model
var Product = new Schema({
title: { type: String, required: true },
description: { type: String, required: true },
style: { type: String, unique: true },
images: [Images],
categories: [Categories],
catalogs: [Catalogs],
variants: [Variants],
modified: { type: Date, default: Date.now }
});
The embedded documents are in square brackets (above) in the product model. I referenced the Mongoose documentation to
learn how to assemble this model with embedded documents.
Below are the schema assignments that together assemble the product document to store in MongoDB. My strategy is adding
one embedded document at time and updating each the CREATE and UPDATE methods, stopping and restarting the application
03/04/14 Pixelhandler's Blog
pixelhandler.com/posts/develop-a-restful-api-using-nodejs-with-express-and-mongoose 8/24
( control-c then node app.js ) with each iteration. And working out the additional code by fiddling with the same jQuery $.ajax
requests, but also adding the single attribute(s) added to the post data to create a new product document in the db.
// Schemas
var Sizes = new Schema({
size: { type: String, required: true },
available: { type: Number, required: true, min: 0, max: 1000 },
sku: {
type: String,
required: true,
validate: [/[a-zA-Z0-9]/, 'Product sku should only have letters and numbers']
},
price: { type: Number, required: true, min: 0 }
});
var Images = new Schema({
kind: {
type: String,
enum: ['thumbnail', 'catalog', 'detail', 'zoom'],
required: true
},
url: { type: String, required: true }
});
var Variants = new Schema({
color: String,
images: [Images],
sizes: [Sizes]
});
var Categories = new Schema({
name: String
});
var Catalogs = new Schema({
name: String
});
For example, I first added the [Images] embedded docuemnt to my product model and tested out the application by updated the
AJAX post which creates the product using the same post as before but with an array of objects with the image attributes for
kind and url , see below:
var Images = new Schema({
kind: String,
url: String
});
var Product = new Schema({
title: { type: String, required: true },
description: { type: String, required: true },
style: { type: String, unique: true },
images: [Images],
modified: { type: Date, default: Date.now }
});
I also updated the CREATE (POST) and UPDATE (PUT) methods, adding the references to the images attribute (an embedded
document) of the product model.
// CREATE a product
app.post('/api/products', function(req, res){
var product;
console.log("POST: ");
console.log(req.body);
03/04/14 Pixelhandler's Blog
pixelhandler.com/posts/develop-a-restful-api-using-nodejs-with-express-and-mongoose 9/24
product = new ProductModel({
title: req.body.title,
description: req.body.description,
style: req.body.style,
images: [Images]
});
product.save(function(err) {
if (!err) {
return console.log("created");
} else {
return console.log(err);
}
});
return res.send(product);
});
// UPDATE a single product
app.put('/api/products/:id', function(req, res){
return ProductModel.findById(req.params.id, function(err, product) {
product.title = req.body.title;
product.description = req.body.description;
product.style = req.body.style;
product.images = req.body.images;
return product.save(function(err) {
if (!err) {
console.log("updated");
} else {
console.log(err);
}
return res.send(product);
});
});
});
Then I worked out the adding the image(s) data to my post that creates a product in the database; addming the images data
array with an object like so:
jQuery.post("/api/products", {
"title": "My Awesome T-shirt",
"description": "All about the details. Of course it's black.",
"style": "1234",
"images": [
{
"kind": "thumbnail",
"url": "images/products/1234/main.jpg"
}
]
}, function(data, textStatus, jqXHR) {
console.log("Post resposne:"); console.dir(data); console.log(textStatus); console.dir(jqXHR);
});
On my first of attempt of adding multiple documents to the product model, I did get errors and the server's create action failed.
However, my terminal (shell) does report the errors - the app.js file uses the code app.use(express.errorHandler({ dumpExceptions: true,
showStack: true })); to setup the display of errors on the command line. Also, I added some console.log calls in the post action to
log the request pluse notes on the execution of saving the document. On both the browser and on the command line, all the
logging indicates whether I am building the product model (using Mongoose) properly. This attempt to add the images did not
save. I am not sure why, but I switched over to adding a [Categories] embedded docuemnt, then worked my way toward a
completed product model with an API to CREATE, UPDATE and DELETE a single product at a time and to READ a single product
or list of products in an array.
After debugging the embedded documents I added for the product models attribtues... now I can create the complete product
in a post:
03/04/14 Pixelhandler's Blog
pixelhandler.com/posts/develop-a-restful-api-using-nodejs-with-express-and-mongoose 10/24
jQuery.post("/api/products", {
"title": "My Awesome T-shirt",
"description": "All about the details. Of course it's black.",
"images": [
{
"kind": "thumbnail",
"url": "images/products/1234/main.jpg"
}
],
"categories": [
{ "name": "Clothes" },
{ "name": "Shirts" }
],
"style": "1234",
"variants": [
{
"color": "Black",
"images": [
{
"kind": "thumbnail",
"url": "images/products/1234/thumbnail.jpg"
},
{
"kind": "catalog",
"url": "images/products/1234/black.jpg"
}
],
"sizes": [
{
"size": "S",
"available": 10,
"sku": "CAT-1234-Blk-S",
"price": 99.99
},
{
"size": "M",
"available": 7,
"sku": "CAT-1234-Blk-M",
"price": 109.99
}
]
}
],
"catalogs": [
{ "name": "Apparel" }
]
}, function(data, textStatus, jqXHR) {
console.log("Post resposne:"); console.dir(data); console.log(textStatus); console.dir(jqXHR);
});
And from the node console (shell) I get this output:
POST:
{ title: 'My Awesome T-shirt',
description: 'All about the details. Of course it\'s black.',
images: [ { kind: 'thumbnail', url: 'images/products/1234/main.jpg' } ],
categories: [ { name: 'Clothes' }, { name: 'Shirts' } ],
style: '1234',
varients: [ { color: 'Black', images: [Object], sizes: [Object] } ],
catalogs: [ { name: 'Apparel' } ] }
validate style
1234
validate description
All about the details. Of course it's black.
validate title
My Awesome T-shirt
created
In my browser this looks like these two screenshot:
03/04/14 Pixelhandler's Blog
pixelhandler.com/posts/develop-a-restful-api-using-nodejs-with-express-and-mongoose 11/24
Ready to post using the console
Server response in the network tab
03/04/14 Pixelhandler's Blog
pixelhandler.com/posts/develop-a-restful-api-using-nodejs-with-express-and-mongoose 12/24
Bulk Actions for UPDATE and DELETE
Finally to finish up the actions needed in the design for the products web service I added the bulk actions to remove all
products at once and also to update many products in a PUT request.
app.delete('/api/products', function (req, res) {
ProductModel.remove(function (err) {
if (!err) {
console.log("removed");
return res.send('');
} else {
console.log(err);
}
});
});
app.put('/api/products', function (req, res) {
var i, len = 0;
console.log("is Array req.body.products");
console.log(Array.isArray(req.body.products));
console.log("PUT: (products)");
console.log(req.body.products);
if (Array.isArray(req.body.products)) {
len = req.body.products.length;
}
for (i = 0; i < len; i++) {
03/04/14 Pixelhandler's Blog
pixelhandler.com/posts/develop-a-restful-api-using-nodejs-with-express-and-mongoose 13/24
console.log("UPDATE product by id:");
for (var id in req.body.products[i]) {
console.log(id);
}
ProductModel.update({ "_id": id }, req.body.products[i][id], function (err, numAffected) {
if (err) {
console.log("Error on update");
console.log(err);
} else {
console.log("updated num: " + numAffected);
}
});
}
return res.send(req.body.products);
});
See the Gist links that follow for sample scripts to create many products (fixtures) and also the bulk update with single AJAX
PUT request.
The app.js, index.html and jQuery AJAX snippets developed in this tutorial
The Source Code for This Tutorial is on GitHub as a Gist:
Develop a RESTful API Using Node.js With Express and Mongoose
Fixtures - example AJAX posts to create products
Sample script for bulk update of products
The application file:
var application_root = __dirname,
express = require("express"),
path = require("path"),
mongoose = require('mongoose');
var app = express.createServer();
// database
mongoose.connect('mongodb://localhost/ecomm_database');
// config
app.configure(function () {
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(path.join(application_root, "public")));
app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});
var Schema = mongoose.Schema; //Schema.ObjectId
// Schemas
var Sizes = new Schema({
size: { type: String, required: true },
available: { type: Number, required: true, min: 0, max: 1000 },
sku: {
type: String,
required: true,
validate: [/[a-zA-Z0-9]/, 'Product sku should only have letters and numbers']
},
price: { type: Number, required: true, min: 0 }
});
var Images = new Schema({
kind: {
type: String,
enum: ['thumbnail', 'catalog', 'detail', 'zoom'],
03/04/14 Pixelhandler's Blog
pixelhandler.com/posts/develop-a-restful-api-using-nodejs-with-express-and-mongoose 14/24
required: true
},
url: { type: String, required: true }
});
var Variants = new Schema({
color: String,
images: [Images],
sizes: [Sizes]
});
var Categories = new Schema({
name: String
});
var Catalogs = new Schema({
name: String
});
// Product Model
var Product = new Schema({
title: { type: String, required: true },
description: { type: String, required: true },
style: { type: String, unique: true },
images: [Images],
categories: [Categories],
catalogs: [Catalogs],
variants: [Variants],
modified: { type: Date, default: Date.now }
});
// validation
Product.path('title').validate(function (v) {
console.log("validate title");
console.log(v);
return v.length > 10 && v.length < 70;
});
Product.path('style').validate(function (v) {
console.log("validate style");
console.log(v);
return v.length < 40;
}, 'Product style attribute is should be less than 40 characters');
Product.path('description').validate(function (v) {
console.log("validate description");
console.log(v);
return v.length > 10;
}, 'Product description should be more than 10 characters');
var ProductModel = mongoose.model('Product', Product);
/* Product Document
[
{
"title": "My Awesome T-shirt",
"description": "All about the details. Of course it's black.",
"images": [
{
"kind": "thumbnail",
"url": "images/products/1234/main.jpg"
}
],
"categories": [
{ "name": "Clothes" },
{ "name": "Shirts" }
],
"style": "1234",
"variants": [
{
"color": "Black",
"images": [
03/04/14 Pixelhandler's Blog
pixelhandler.com/posts/develop-a-restful-api-using-nodejs-with-express-and-mongoose 15/24
{
"kind": "thumbnail",
"url": "images/products/1234/thumbnail.jpg"
},
{
"kind": "catalog",
"url": "images/products/1234/black.jpg"
}
],
"sizes": [
{
"size": "S",
"available": 10,
"sku": "CAT-1234-Blk-S",
"price": 99.99
},
{
"size": "M",
"available": 7,
"sku": "CAT-1234-Blk-M",
"price": 109.99
}
]
}
],
"catalogs": [
{ "name": "Apparel" }
]
}
]
*/
// REST api
app.get('/api', function (req, res) {
res.send('Ecomm API is running');
});
// POST to CREATE
app.post('/api/products', function (req, res) {
var product;
console.log("POST: ");
console.log(req.body);
product = new ProductModel({
title: req.body.title,
description: req.body.description,
style: req.body.style,
images: req.body.images,
categories: req.body.categories,
catalogs: req.body.catalogs,
variants: req.body.variants
});
product.save(function (err) {
if (!err) {
return console.log("created");
} else {
return console.log(err);
}
});
return res.send(product);
});
// PUT to UPDATE
// Bulk update
app.put('/api/products', function (req, res) {
var i, len = 0;
console.log("is Array req.body.products");
console.log(Array.isArray(req.body.products));
console.log("PUT: (products)");
console.log(req.body.products);
if (Array.isArray(req.body.products)) {
len = req.body.products.length;
03/04/14 Pixelhandler's Blog
pixelhandler.com/posts/develop-a-restful-api-using-nodejs-with-express-and-mongoose 16/24
}
for (i = 0; i < len; i++) {
console.log("UPDATE product by id:");
for (var id in req.body.products[i]) {
console.log(id);
}
ProductModel.update({ "_id": id }, req.body.products[i][id], function (err, numAffected) {
if (err) {
console.log("Error on update");
console.log(err);
} else {
console.log("updated num: " + numAffected);
}
});
}
return res.send(req.body.products);
});
// Single update
app.put('/api/products/:id', function (req, res) {
return ProductModel.findById(req.params.id, function (err, product) {
product.title = req.body.title;
product.description = req.body.description;
product.style = req.body.style;
product.images = req.body.images;
product.categories = req.body.categories;
product.catalogs = req.body.catalogs;
product.variants = req.body.variants;
return product.save(function (err) {
if (!err) {
console.log("updated");
} else {
console.log(err);
}
return res.send(product);
});
});
});
// GET to READ
// List products
app.get('/api/products', function (req, res) {
return ProductModel.find(function (err, products) {
if (!err) {
return res.send(products);
} else {
return console.log(err);
}
});
});
// Single product
app.get('/api/products/:id', function (req, res) {
return ProductModel.findById(req.params.id, function (err, product) {
if (!err) {
return res.send(product);
} else {
return console.log(err);
}
});
});
// DELETE to DESTROY
// Bulk destroy all products
app.delete('/api/products', function (req, res) {
ProductModel.remove(function (err) {
if (!err) {
console.log("removed");
return res.send('');
} else {
console.log(err);
}
03/04/14 Pixelhandler's Blog
pixelhandler.com/posts/develop-a-restful-api-using-nodejs-with-express-and-mongoose 17/24
});
});
// remove a single product
app.delete('/api/products/:id', function (req, res) {
return ProductModel.findById(req.params.id, function (err, product) {
return product.remove(function (err) {
if (!err) {
console.log("removed");
return res.send('');
} else {
console.log(err);
}
});
});
});
// launch server
app.listen(4242);
Also in the app.js gist (above), I added code to validate the product model using Mongoose.
The index file (inside /public directory):
API index
Nouns...
/products
/products/:id
Some jQuery AJAX snippets to fiddle with the API:
// jQuery snippets used in the console to use the REST api created with app.js
// CREATE
jQuery.post("/api/products", {
"title": "My Awesome T-shirt",
"description": "All about the details. Of course it's black.",
"images": [
{
"kind": "thumbnail",
"url": "images/products/1234/main.jpg"
}
],
"categories": [
{ "name": "Clothes" },
{ "name": "Shirts" }
],
"style": "1234",
"variants": [
{
"color": "Black",
"images": [
{
"kind": "thumbnail",
03/04/14 Pixelhandler's Blog
pixelhandler.com/posts/develop-a-restful-api-using-nodejs-with-express-and-mongoose 18/24
"url": "images/products/1234/thumbnail.jpg"
},
{
"kind": "catalog",
"url": "images/products/1234/black.jpg"
}
],
"sizes": [
{
"size": "S",
"available": 10,
"sku": "CAT-1234-Blk-S",
"price": 99.99
},
{
"size": "M",
"available": 7,
"sku": "CAT-1234-Blk-M",
"price": 109.99
}
]
}
],
"catalogs": [
{ "name": "Apparel" }
]
}, function(data, textStatus, jqXHR) {
console.log("Post resposne:"); console.dir(data); console.log(textStatus); console.dir(jqXHR);
});
// generated a product document with automatically assigned ID, e.g. 4f34734d21289c1c28000007
// READ
jQuery.get("/api/products/", function(data, textStatus, jqXHR) {
console.log("Post resposne:");
console.dir(data);
console.log(textStatus);
console.dir(jqXHR);
});
jQuery.get("/api/products/4f34734d21289c1c28000007", function(data, textStatus, jqXHR) {
console.log("Post resposne:");
console.dir(data);
console.log(textStatus);
console.dir(jqXHR);
});
// UPDATE
jQuery.ajax({
url: "/api/products/4f34734d21289c1c28000007",
type: "PUT",
data: {
"title": "My Awesome T-shirt",
"description": "All about the details. Of course it's black, and longsleeve.",
"images": [
{
"kind": "thumbnail",
"url": "images/products/1234/main.jpg"
}
],
"categories": [
{ "name": "Clothes" },
{ "name": "Shirts" }
],
"style": "1234",
"variants": [
{
"color": "Black",
"images": [
{
"kind": "zoom",
03/04/14 Pixelhandler's Blog
pixelhandler.com/posts/develop-a-restful-api-using-nodejs-with-express-and-mongoose 19/24
"url": "images/products/1234/zoom.jpg"
}
],
"sizes": [
{
"size": "L",
"available": 77,
"sku": "CAT-1234-Blk-L",
"price": 99.99
}
]
}
],
"catalogs": [
{ "name": "Apparel" }
]
},
success: function(data, textStatus, jqXHR) {
console.log("PUT resposne:");
console.dir(data);
console.log(textStatus);
console.dir(jqXHR);
}
});
// Delete
jQuery.ajax({url: "/api/products/4f34734d21289c1c28000007", type: "DELETE", success: function(data, textStatus, jqXHR) { console.dir(data); }});
Post Hoc
This tutorial came about as a desire to develop with a local API. Using a local API, I can develop a client application with
Backbone.js and utilize the asynchronous behaviors that come with the API. I am not suggesting that anyone uses this tutorial
to build a RESTful API for a production ecommerce application. However, I do advocate developing with a local API rather then
just mocking a server without asynchronous interations with JSON data. If you are not working with a RESTful API and are not
consuming data using AJAX, in a few hours you can be.
JavaScript runs in so many applications, and since I already know JavaScript I would rather fiddle with Node.js than build an API
for my local development needs in PHP or Ruby. Also, this exercise helps me to understand more about JSON, REST and jQuery
AJAX development. Getting to know these technologies and developing solid skills using asynchronous behavior, necessary to
build HTML5 apps for desktop and/or mobile browsers.
Completing this tutorial will likely take a few hours, even longer if you do not have node and npm running on your development
environment.
Reference
API design nouns are good, verbs are bad.
Installing Node.js
npm is a package manager for node.
Models are defined by passing a Schema instance to mongoose.model
SchemaTypes take care of validation, casting, defaults, and other general options in our models
Embedded documents are documents with schemas of their own that are part of other documents
Backbone Todo boilerplates demonstrating integration with Node.js, Express, MongoDB
MongoDB (from 'humongous') - Document-oriented storage
MongoDB Quickstart
Try manipulating the Mongo database with the database shell or MongoDB browser shell
Mongoose is a MongoDB object modeling tool designed to work in an asynchronous environment.
High performance, high class Web development for Node.js
npm install express
Using Node.js, Express, Mongoose and MongoDB
53 Comments Pixelhandler's Blog Login
03/04/14 Pixelhandler's Blog
pixelhandler.com/posts/develop-a-restful-api-using-nodejs-with-express-and-mongoose 20/24
53 Comments Pixelhandler's Blog Login
Sort by Best Share
Join the discussion
Reply
Andrew a year ago
Can someone tell me why it would be bad to use this as production code and what needs to be added to make it acceptable?
14
Reply
Matt Vleming 3 months ago Andrew
I too would like to hear these two questions answered. Is there a guide someone can recommend for securing APIs built w/ Node.JS
for production?
Reply
Ben Clayton a year ago
Note that (at least with the current version of express) to route an HTTP delete call you use app.del("URL", function) NOT app.delete("URL",
function) as the code examples above say. 'delete' is a reserved word in JavaScript.
9
Reply
Glorydeath a year ago
This is very useful! Thank you!
I'm trying to develop a restful API using nodejs. And, apart from json, I would also serialize it into xml and rdf. Can you please tell me how to
achieve those? Thanks.
3
Reply
zgollum a year ago
Are you leaving your clients hanging and waiting for response instead of sending a proper 404 in case no document is found in your get
handler?
4
Reply
jsmarkus 2 years ago
Nice post, thank you!
Some months ago I developed a RESTful application like this. But later on - the more resources it served, the more boring it became to write
code. So I decided to write RESTful framework that simplifies developing such things.
Meet: https://github.com/jsmarkus/co...
It is still under development, but new parts of my application are written with it. And I believe the Colibri project will grow, because it is not a
just-for-fun thing.
I encourage you guys to join me in open-source development of Colibri framework :)
1
Reply
Bill Heaton 2 years agopixelhandler
Today I deployed this ecomapi to Heroku at : http://ecomapi.herokuapp.com/ the snippets for using the API are here
https://gist.github.com/179108... ; to see a list of products in the mongo db generated with Backbone.js code add #list -
http://ecomapi.herokuapp.com/#... to work with the api use the javascript console and the code snippets in the gists.
1
Reply
Patrick J. Jankun a year ago Bill Heaton
ooh. I missed that it's a bulk action ;)
Reply
Xiaohero1990 2 years ago
Hi,I want ask a question.If I want to use 'express',I must run "express newApp".It just product a lot of useless files.
Reply
lewdaly 4 months ago
Thanks so much! Excellent post.
Reply
LH 8 months ago
Thank you for the excellent post
Ado Trakic 11 months ago
Really nice tutorial - thanks for putting this together. Executed parts of it, hopefully will do the whole tutorial. How are you planning to handle
developers access to your APIs, tracking usage, impose limits etc.?
Favorite
Share
Share
Share
Share
Share
Share
Share
Share
Share
Share
Share
03/04/14 Pixelhandler's Blog
pixelhandler.com/posts/develop-a-restful-api-using-nodejs-with-express-and-mongoose 21/24
Reply
Is there another tool that can serve this purpose or we need to use API Management platforms like Mashery, Apigee, Apiphany etc.?
Thanks.
~ Ado
about.me/adotrakic
Reply
Hugo Dias 11 months ago
Great post man. Im using some lines of your code to build my own Node + Mongo + AngularJS App. Thank you so much
Reply
winered 11 months ago
so cool, thanks a lot!
Reply
lricoy a year ago
One thing thought, on the line 10 of create.js you use images: [Images] instead of req.body.images;
Reply
Ben Duncan a year ago
This is still a great tutorial for building RESTful APIs for Node. It's got some real good fundamentals in here and goes into realistic detail about
stuff you need to worry about as far as Node specifics and basics, as well as those of Express.
Spectacular job, thanks!
Reply
Andrew a year ago
To edit and delete actions I added:
if(product === undefined) return res.send(404);
right after the findById call to deal with invalid product calls and
for get all and get one product calls I modified
return console.log(err)' to
console.log(err);
return res.send(404);
to deal with invalid product calls.
Reply
pmjtoca a year ago
Hi. I am reading a lot of tech. docs and tuts as a soft.quality auditor... Just to say that your tuts. is excellent in terms of pedagogy. Vraiment
Bravo!!
Reply
alfared a year ago
Thanks for the very good article. It helped me to understand the principles and methods of REST.
Reply
ShloopyD a year ago
Have you tried putting the routers into their own files?
Reply
Simon a year ago
Excellent post. Thank you for all the details!
Reply
Oliver Fischer a year ago
Awesome stuff, really helpful for coding...
Reply
Joel Kang a year ago
So many hours saved because of this tutorial. Thanks so much!
Reply
Suvi Vignarajah a year ago
awesome blog entry with a thorough tutorial of how node, mongoose and mongodb all tie in together
marcoslhc a year ago
I'm new in this node.js express thing. This is the most useful article in the subject I've found. Thanks
Share
Share
Share
Share
Share
Share
Share
Share
Share
Share
Share
Share
Share
03/04/14 Pixelhandler's Blog
pixelhandler.com/posts/develop-a-restful-api-using-nodejs-with-express-and-mongoose 22/24
Reply
I'm new in this node.js express thing. This is the most useful article in the subject I've found. Thanks
Reply
sean a year ago
Great post, thanks
Reply
pixelBender67 a year ago
Wow, this is a great article, thanks so much!
Reply
Denis O'Sullivan a year ago
very well written Bill, clearly you put a lot of effort into this, lots for me to learn, thank you
Reply
2 years ago
really liked the link because reall effort has been put on this link to make it successful.
Reply
Utuxia Consulting 2 years ago
Great to see some new energy in the node community w/ respect to tutorials.
Reply
nzru 2 years ago
Great post!
Reply
Evan 2 years ago
Nice post! very helpful!
Reply
Andy 2 years ago
very ql!
Reply
Sergey Romanov 2 years ago
This is excellent tutorial. I have got only one problem
Warning: express.createServer() is deprecated, expressapplications no longer inherit from http.Server,please use:
var express = require("express"); var app = express();
What do I do? Can you update your tutorial according to new changes? I need it very much.
Reply
Adam Gibbons a year ago Sergey Romanov
Just replace:
var app = express.createServer();
with this:
var app = express();
Reply
Brad Proctor 2 years ago
Excellent tutorial. This is exact what I've been looking for.
Reply
prasadgc 2 years ago
Hi, thanks for this. It's a great tutorial. However, the final version of app.js in your Gist doesn't run. It fails with the following error:
events.js:66
throw arguments[1]; // Unhandled 'error' event
^
Error: listen EADDRINUSE
at errnoException (net.js:781:11)
at Server._listen2._connectionKey (net.js:922:26)
at process.startup.processNextTick.process._tickCallback (node.js:244:9)
What am I missing?
Bill Heaton 2 years agopixelhandler prasadgc
Did you look at the source found here: https://github.com/pixelhandle... this is the repository for the working demo on heroku:
http://ecomapi.herokuapp.com/
Share
Share
Share
Share
Share
Share
Share
Share
Share
Share
Share
Share
Share
03/04/14 Pixelhandler's Blog
pixelhandler.com/posts/develop-a-restful-api-using-nodejs-with-express-and-mongoose 23/24
Load more comments
Scaffold for a browser app built with Ember.js, Rails,Ember.Data
3 comments a month ago
Using Ember.StateManager: Example Apps
1 comment 8 months ago
Erik Sundell thanks for this =) // consideRatio @ irc
ALSO ON PIXELHANDLER'S BLOG
Reply
http://ecomapi.herokuapp.com/
Reply
Donald Pipowitch 2 years ago
Thank you very much. Your post was an enormous help for me.
Reply
UnInvitado 2 years ago
thank you very much for this post! very good introduction for combining different technologies.
I would recommend you to update this tutorial using socket.io, and in particular, backbone.iobin, a project that simply override the .sync
libraries with socket.io. awesome.
thanks again!
Reply
Gregory 2 years ago UnInvitado
How would you change to use socket.io? REST over Ajax seems to work.
Reply
Edmond Lau 2 years ago
This is very useful. Thanks you!
Reply
Vance Lucas 2 years ago
Great tutorial. You might want to check out Frisby.js ( http://frisbyjs.com ) for your API tests though instead of using jQuery - it's a bit cleaner,
and can be eventually integrated with CI tools when the code goes to production.
Reply
imjp 2 years ago
You have no idea how thankful I am for this post! I've been looking like crazy for a "decent" one and this one just simply blows all of the other
posts out of the water :D awesome post!
Reply
Mike 2 years ago
Great post! I have a very similar API set up but I'm wondering how to handle a GET when instead of an embedded document you store an
array of ObjectIDs (linking)? This would probably require a 2nd GET, correct?
Reply
myousufq 2 years ago
Nice post. thanks
Reply
Bill Heaton 2 years agopixelhandler
I updated this post adding the code to app.js for the bulk actions for UPDATE and DELETE. Also added to the gist code a fixtures.js file to
use to create products following a bulk delete, and a bulk-updates.js script to for AJAX PUT request to update multiple products.
Also added to the gist is Backbone.js code to render a list of the products using this API, see: https://gist.github.com/179108...
Reply
Robin Dang 7 months ago Bill Heaton
I tested it with delete command with id parameter and it called the one that deletes all?
Reply
Bill Heaton 7 months agopixelhandler Robin Dang
I tried out the demo code running here: http://ecomapi.herokuapp.com/#... and was able to choose an `_id` then delete just
that id. It's likely that I need to update this tutorial it's been about a year and a half since I posted it.
Reply
Ash 2 years ago
Really helpful tutorial. I'm messing around with MongoDB right now and trying to decide which node.js module to use in combination with it.
So this tutorial is perfectly timed! Thanks.
WHAT'S THIS?
Share
Share
Share
Share
Share
Share
Share
Share
Share
Share
Share
Share
Share
03/04/14 Pixelhandler's Blog
pixelhandler.com/posts/develop-a-restful-api-using-nodejs-with-express-and-mongoose 24/24
JeffreyBiles Thanks for putting this together! Even though I've
been doing ember for a while, this is a great checklist for when I
start a new project or add a new
Erik Sundell thanks for this =) // consideRatio @ irc
Create a Custom Select Box using Ember.Component
1 comment a month ago
Abhaya Thapa Good Read. Is there any way we can replace
select with ul li and fake it as select box ?? We all know there is
huge restriction when it comes to
Pixelhandler's Blog
2 comments a month ago
aevo The response time and rendering speed are amazing.
Good job.
Subscribe Add Disqus to your site
Recent Posts
We are EmberConf 2014
End-to-end Javascript Testing: Integration Tests Using Ember.js Test Helpers
Refreshed my Blog with Express and Ember.js
Scaffold for a browser app built with Ember.js, Rails, Ember.Data
Create a Custom Select Box using Ember.Component
Testing an Ember Application: Integration and Unit tests
Using Ember.StateManager: Example Apps
Bowling Game Kata Using Mocha (BDD) Test Framework and Yeoman
Backbone.js Models, Views and Collections to Present API Data
Develop a RESTful API Using Node.js With Express and Mongoose
Links
Blog
Archive
About
The End. [Personal blog, Copyright @2014 Bill Heaton] | Admin