develop a restful api using node.js with express and mongoose

Upload: orangota

Post on 17-Oct-2015

381 views

Category:

Documents


0 download

TRANSCRIPT

  • 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