Create a Web App and RESTful API Server Using the MEAN Stack

Last updated 16 May 2016

The MEAN stack is a popular web development stack made up of MongoDB, Express, AngularJS, and Node.js. MEAN has gained popularity because it allows developers to program in JavaScript on both the client and the server. The MEAN stack enables a perfect harmony of JavaScript Object Notation (JSON) development: MongoDB stores data in a JSON-like format, Express and Node.js facilitate easy JSON query creation, and AngularJS allows the client to seamlessly send and receive JSON documents.

MEAN is generally used to create browser-based web applications because AngularJS (client-side) and Express (server-side) are both frameworks for web apps. Another compelling use case for MEAN is the development of RESTful API servers. Creating RESTful API servers has become an increasingly important and common development task, as applications increasingly need to gracefully support a variety of end-user devices, such as mobile phones and tablets. This tutorial will demonstrate how to use the MEAN stack to rapidly create a RESTful API server.

AngularJS, a client-side framework, is not a necessary component for creating an API server. You could also write an Android or iOS application that runs on top of the REST API. We include AngularJS in this tutorial to demonstrate how it allows us to quickly create a web application that runs on top of the API server.

The application we will develop in this tutorial is a basic contact management application that supports standard CRUD (Create, Read, Update, Delete) operations. First, we’ll create a RESTful API server to act as an interface for querying and persisting data in a MongoDB database. Then, we’ll leverage the API server to build an Angular-based web application that provides an interface for end users.

So that we can focus on illustrating the fundamental structure of a MEAN application, we will deliberately omit common functionality such as authentication, access control, and robust data validation.

Prerequisites

If you have never deployed a Node.js application to Heroku before, we recommend going through the Getting Started with Node.js on Heroku tutorial before you begin.

Ensure that you have the following installed on your local machine:

Source code structure

The source code for this project is available on GitHub at https://github.com/chrisckchang/mean-contactlist. The repository contains:

  • package.json
    • A configuration file that contains metadata about your application. Also, when there is a package.json file in the root directory of the project, Heroku will use the Node.js buildpack.
  • app.json
    • A manifest format for describing web apps. It declares environment variables, add-ons, and other information required to run an app on Heroku. It is required to create a “Deploy to Heroku” button.
  • server.js
    • This file contains all of our server-side code, which implements our REST API. It’s written in Node.js, using the Express framework and the MongoDB Node.js driver.
  • /public directory
    • This directory contains all of the client-side files which includes the AngularJS code.

See the sample application running

To see a running version of the application this tutorial will create, you can deploy the application to Heroku with a single click:

Deploy

Now, let’s follow the tutorial step by step.

Create a new app

Create a new directory for your app and use the cd command to navigate to that directory. From this directory, we’ll create an app on Heroku which prepares Heroku to receive your source code. We’ll use the Heroku CLI to get started.

$ git init
Initialized empty Git repository in /path/.git/
$ heroku create
Creating app... done, stack is cedar-14
https://sleepy-citadel-45065.herokuapp.com/ | https://git.heroku.com/sleepy-citadel-45065.git

When you create an app, a git remote (called heroku) is also created and associated with your local git repository. Heroku also generates a random name (in this case sleepy-citadel-45065) for your app.

Heroku recognizes an app as Node.js by the existence of a package.json file in the root directory. Create a file called package.json and copy the following into it:

{
  "name": "MEAN",
  "version": "1.0.0",
  "description": "A MEAN app that allows users to manage contact lists",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node server.js"
  },
  "dependencies": {
    "body-parser": "^1.13.3",
    "express": "^4.13.3",
    "mongodb": "^2.1.6"
  }
}

The package.json file determines the version of Node.js that will be used to run your application on Heroku, as well as the dependencies that should be installed with your application. When an app is deployed, Heroku reads this file and installs the appropriate Node.js version together with the dependencies using the npm install command.

To prepare your system for running the app locally, run this command in your local directory to install the dependencies:

$ npm install

After dependencies are installed, you will be ready to run your app locally.

Provision a MongoDB database

After you set up your application and file directory, create a MongoDB instance to persist your application’s data. We’ll use the mLab add-on, a fully managed MongoDB service, to easily provision a new MongoDB database.

Add the mLab add-on to your app. To do this, at the Heroku CLI run this command to create a new Sandbox database:

$ heroku addons:create mongolab

When you create a mLab add-on, the database connection URI is stored as a config var. You can access this variable in Node.js as process.env.MONGODB_URI, which we will do later.

Now that our database is ready, we can start coding.

Connect MongoDB and the app server using the Node.js driver

There are two popular MongoDB drivers that Node.js developers use: the official Node.js driver and an object document mapper called Mongoose that wraps the Node.js driver (similar to a SQL ORM). Both have their advantages, but for this example we will use the official Node.js driver.

Create a file called server.js. In this file we’ll create a new Express application and connect to our mLab database.

var express = require("express");
var path = require("path");
var bodyParser = require("body-parser");
var mongodb = require("mongodb");
var ObjectID = mongodb.ObjectID;

var CONTACTS_COLLECTION = "contacts";

var app = express();
app.use(express.static(__dirname + "/public"));
app.use(bodyParser.json());

// Create a database variable outside of the database connection callback to reuse the connection pool in your app.
var db;

// Connect to the database before starting the application server.
mongodb.MongoClient.connect(process.env.MONGODB_URI, function (err, database) {
  if (err) {
    console.log(err);
    process.exit(1);
  }

  // Save database object from the callback for reuse.
  db = database;
  console.log("Database connection ready");

  // Initialize the app.
  var server = app.listen(process.env.PORT || 8080, function () {
    var port = server.address().port;
    console.log("App now running on port", port);
  });
});

// CONTACTS API ROUTES BELOW

There are a few things to note regarding connecting to the database:

  • We want to use our database connection pool as often as possible to best manage our available resources. We initialize the db variable in the global scope so that the connection can be used by all the route handlers.
  • We initialize the app only after the database connection is ready. This ensures that the application won’t crash or error out by trying database operations before the connection is established.

Now our app and database are connected. Next we will implement the RESTful API server by first defining all the endpoints.

Create a RESTful API server with Node.js and Express

As our first step in creating the API, we define the endpoints (or data) we want to expose. Our contact list app will allow users to perform CRUD operations on their contacts.

The endpoints we’ll need are:

/contacts

Method Description
GET Find all contacts
POST Create a new contact

/contacts/:id

Method Description
GET Find a single contact by ID
PUT Update entire contact document
DELETE Delete a contact by ID

Now we’ll add the routes to our server.js file:

경축! 아무것도 안하여 에스천사게임즈가 새로운 모습으로 재오픈 하였습니다.
어린이용이며, 설치가 필요없는 브라우저 게임입니다.
https://s1004games.com

// CONTACTS API ROUTES BELOW

// Generic error handler used by all endpoints.
function handleError(res, reason, message, code) {
  console.log("ERROR: " + reason);
  res.status(code || 500).json({"error": message});
}

/*  "/contacts"
 *    GET: finds all contacts
 *    POST: creates a new contact
 */

app.get("/contacts", function(req, res) {
});

app.post("/contacts", function(req, res) {
});

/*  "/contacts/:id"
 *    GET: find contact by id
 *    PUT: update contact by id
 *    DELETE: deletes contact by id
 */

app.get("/contacts/:id", function(req, res) {
});

app.put("/contacts/:id", function(req, res) {
});

app.delete("/contacts/:id", function(req, res) {
});

The code creates a skeleton for all of the API endpoints defined above.

Implement the API endpoints

Next, we’ll add in database logic to properly implement these endpoints.

We’ll first implement the POST endpoint for “/contacts”, which will allow us to create and save new contacts to the database. Each contact will have the following schema:

{
  "_id": <ObjectId>
  "firstName": <string>,
  "lastName": <string>,
  "email": <string>,
  "phoneNumbers": {
    "mobile": <string>,
    "work": <string>
  },
  "twitterHandle": <string>,
  "addresses": {
    "home": <string>,
    "work": <string>
  }
}

The following code implements the /contacts POST request:

app.post("/contacts", function(req, res) {
  var newContact = req.body;
  newContact.createDate = new Date();

  if (!(req.body.firstName || req.body.lastName)) {
    handleError(res, "Invalid user input", "Must provide a first or last name.", 400);
  }

  db.collection(CONTACTS_COLLECTION).insertOne(newContact, function(err, doc) {
    if (err) {
      handleError(res, err.message, "Failed to create new contact.");
    } else {
      res.status(201).json(doc.ops[0]);
    }
  });
});

To test the POST implementation, deploy the code:

$ git add package.json
$ git add server.js
$ git commit -m 'first commit'
$ git push heroku master

The application is now deployed. Ensure that at least one instance of the app is running:

$ heroku ps:scale web=1

Then, use cURL to issue a POST request:

curl -H "Content-Type: application/json" -d '{"firstName":"Chris", "lastName": "Chang", "email": "support@mlab.com"}' http://your-app-name.herokuapp.com/contacts

We haven’t created our web app yet, but you can confirm that the data was successfully saved to the database by visiting the mLab management portal. Your new contact should be displayed in the “contacts” collection.

Here is the final version of the server.js file, which implements all of the endpoints:

var express = require("express");
var path = require("path");
var bodyParser = require("body-parser");
var mongodb = require("mongodb");
var ObjectID = mongodb.ObjectID;

var CONTACTS_COLLECTION = "contacts";

var app = express();
app.use(express.static(__dirname + "/public"));
app.use(bodyParser.json());

// Create a database variable outside of the database connection callback to reuse the connection pool in your app.
var db;

// Connect to the database before starting the application server.
mongodb.MongoClient.connect(process.env.MONGODB_URI, function (err, database) {
  if (err) {
    console.log(err);
    process.exit(1);
  }

  // Save database object from the callback for reuse.
  db = database;
  console.log("Database connection ready");

  // Initialize the app.
  var server = app.listen(process.env.PORT || 8080, function () {
    var port = server.address().port;
    console.log("App now running on port", port);
  });
});

// CONTACTS API ROUTES BELOW

// Generic error handler used by all endpoints.
function handleError(res, reason, message, code) {
  console.log("ERROR: " + reason);
  res.status(code || 500).json({"error": message});
}

/*  "/contacts"
 *    GET: finds all contacts
 *    POST: creates a new contact
 */

app.get("/contacts", function(req, res) {
  db.collection(CONTACTS_COLLECTION).find({}).toArray(function(err, docs) {
    if (err) {
      handleError(res, err.message, "Failed to get contacts.");
    } else {
      res.status(200).json(docs);
    }
  });
});

app.post("/contacts", function(req, res) {
  var newContact = req.body;
  newContact.createDate = new Date();

  if (!(req.body.firstName || req.body.lastName)) {
    handleError(res, "Invalid user input", "Must provide a first or last name.", 400);
  }

  db.collection(CONTACTS_COLLECTION).insertOne(newContact, function(err, doc) {
    if (err) {
      handleError(res, err.message, "Failed to create new contact.");
    } else {
      res.status(201).json(doc.ops[0]);
    }
  });
});

/*  "/contacts/:id"
 *    GET: find contact by id
 *    PUT: update contact by id
 *    DELETE: deletes contact by id
 */

app.get("/contacts/:id", function(req, res) {
  db.collection(CONTACTS_COLLECTION).findOne({ _id: new ObjectID(req.params.id) }, function(err, doc) {
    if (err) {
      handleError(res, err.message, "Failed to get contact");
    } else {
      res.status(200).json(doc);
    }
  });
});

app.put("/contacts/:id", function(req, res) {
  var updateDoc = req.body;
  delete updateDoc._id;

  db.collection(CONTACTS_COLLECTION).updateOne({_id: new ObjectID(req.params.id)}, updateDoc, function(err, doc) {
    if (err) {
      handleError(res, err.message, "Failed to update contact");
    } else {
      res.status(204).end();
    }
  });
});

app.delete("/contacts/:id", function(req, res) {
  db.collection(CONTACTS_COLLECTION).deleteOne({_id: new ObjectID(req.params.id)}, function(err, result) {
    if (err) {
      handleError(res, err.message, "Failed to delete contact");
    } else {
      res.status(204).end();
    }
  });
});

Set up static files for the web app

Now that our API is complete we will use it to create our web application. The web app allows users to manage contacts from the browser.

Create a public folder in your project’s root directory and copy over the files from the example app’s public folder. The folder includes HTML templates and our AngularJS code.

As you look through the HTML files, you might notice that there’s some unconventional HTML code, such as “ng-view” in the index.html file:

<div class="container" ng-view>

These extensions are features of AngularJS’s template system. Templates allow us to reuse code and dynamically generate views for the end user.

Build the web app with AngularJS

We’ll use AngularJS to tie everything together. AngularJS will help us to route user requests, render different views, and send data to and from the database.

Our AngularJS code resides in the /public/js folder in the app.js file. To simplify things, we’ll focus solely on the code that is required to retrieve and display contacts when the default homepage route (“/”) is requested. Implementing this functionality requires that we:

  • render the appropriate view and template using the AngularJS routeProvider (index.html and list.html).
  • fetch the contacts from the database using an AngularJS service (GET /contacts).
  • pass the data from the service to the view with an AngularJS controller (ListController).

The code looks like the following:

angular.module("contactsApp", ['ngRoute'])
  .config(function($routeProvider) {
    $routeProvider
      .when("/", {
        templateUrl: "list.html",
        controller: "ListController",
        resolve: {
          contacts: function(Contacts) {
              return Contacts.getContacts();
          }
        }
      })
  })
  .service("Contacts", function($http) {
    this.getContacts = function() {
      return $http.get("/contacts").
        then(function(response) {
            return response;
        }, function(response) {
            alert("Error retrieving contacts.");
        });
    }
  })
  .controller("ListController", function(contacts, $scope) {
    $scope.contacts = contacts.data;
  });

Next, we’ll cover each part of the code and what it does.

Route user requests with AngularJS routeProvider

The routeProvider module helps us configure routes in AngularJS.

angular.module("contactsApp", ['ngRoute'])
  .config(function($routeProvider) {
    $routeProvider
      .when("/", {
        templateUrl: "list.html",
        controller: "ListController",
        resolve: {
          contacts: function(Contacts) {
              return Contacts.getContacts();
          }
        }
      })
  })

The homepage route consists of a few components:

  • the templateUrl, which specifies which template to display
  • the Contacts service, which requests all of the contacts from the API server
  • the ListController, which allows us to add data to the scope and access it from our views.

Use AngularJS services to make requests to the API server

An AngularJS service generates an object that can be used by the rest of the application. Our service acts as the client-side wrapper for all of our API endpoints.

The homepage route uses the getContacts function to request the contacts data.

.service("Contacts", function($http) {
  this.getContacts = function() {
    return $http.get("/contacts").
      then(function(response) {
        return response;
      }, function(response) {
        alert("Error retrieving contacts.");
      });
  }

Our service functions leverage the built-in AngularJS $http service to generate HTTP requests. The module also returns a promise, which you can modify to add additional functionality (such as logging).

Note that with the $http service we use relative URL paths (for example, “/contacts”) as opposed to absolute paths like “app-name.herokuapp.com/contacts”.

Augment our scope using AngularJS controllers

So far, we’ve configured our route, defined a template to display, and retrieved our data using our “Contacts” service. To tie everything together, we’ll create a controller.

.controller("ListController", function(contacts, $scope) {
  $scope.contacts = contacts.data;
})

Our controller adds the contacts data from our service to the homepage scope as a variable named “contacts”. This allows us to access the data directly from the template (“list.html”). We can iterate over the contacts data with AngularJS’s built-in ngRepeat directive:

<div class="container">
  <table class="table table-hover">
    <tbody>
      <tr ng-repeat="contact in contacts | orderBy:'lastName'" style="cursor:pointer">
        <td>
          <a ng-href="#/contact/{{contact._id}}">{{ contact.firstName }} {{ contact.lastName }}</a>
        </td>
      </tr>
    </tbody>
  </table>
</div>

Completing the project

Now that we have an in-depth understanding of how we implemented the homepage route in AngularJS, the implementation for the rest of the web app routes can be found in the source project’s /public/js/app.js file. They all require a route definition in the routeProvider, one or more service functions to make the appropriate HTTP requests, and a controller to augment the scope.

Once you have completed the Angular code, deploy the app again:

$ git add server.js
$ git add public
$ git commit -m 'second commit'
$ git push heroku master

Now that the web application component is complete, you can view your app by opening the website from the CLI:

$ heroku open

Summary

In this tutorial, you learned how to:

  • create a RESTful API server in Express and Node.js.
  • connect a MongoDB database to the API server for querying and persisting data.
  • create a rich web app using AngularJS.

We hope that you have seen the power of the MEAN stack to enable the development of common components for today’s web applications.

 

[출처] https://devcenter.heroku.com/articles/mean-apps-restful-api

 

본 웹사이트는 광고를 포함하고 있습니다.
광고 클릭에서 발생하는 수익금은 모두 웹사이트 서버의 유지 및 관리, 그리고 기술 콘텐츠 향상을 위해 쓰여집니다.
번호 제목 글쓴이 날짜 조회 수
43 [웹 기획] 2) 웹기획의 기본 - 웹사이트 구축을 위한 조직 형태와 역할 file 졸리운_곰 2016.10.08 641
42 [웹 기획] 1) 웹기획의 기본 - 웹기획자는 누구인가 file 졸리운_곰 2016.10.08 601
41 반응형 웹 기획 file 졸리운_곰 2016.10.08 699
40 한국형웹콘텐츠접근성지침2.1 file 졸리운_곰 2016.10.08 675
39 웹표준 가이드 firefox 사이트 crossbrowsing-v2 file 졸리운_곰 2016.10.08 396
38 [jQueryUI] jqGrid 사용하기 - 2 file 졸리운_곰 2016.08.28 947
37 [jQueryUI] 기본 개념 및 사용하기 - 1 file 졸리운_곰 2016.08.28 376
36 A very long AngularJS tutorial file 졸리운_곰 2016.07.21 900
35 AngularJS 소개 - 특징, 철학, 주요 개념, 동작 방식 file 졸리운_곰 2016.07.20 595
34 angularjs 에서 php 서비스 콜하고 json으로 리턴 받기 file 졸리운_곰 2016.07.19 645
33 자바스크립트 프레임워크_AngularJS 기본개념 강좌 file 졸리운_곰 2016.07.19 563
32 [번역] 하루만에 끝내는 AngularJS 졸리운_곰 2016.07.19 790
31 [Node.js] Node.js, Express, MongoDB를 이용하여 REST API 만들기 file 졸리운_곰 2016.06.06 771
30 Writing a Javascript REST client 졸리운_곰 2016.06.06 366
» Create a Web App and RESTful API Server Using the MEAN Stack file 졸리운_곰 2016.06.05 2119
28 [인공지능 자바스크립트] Neural Networks in JavaScript with Brain.js file 졸리운_곰 2016.06.05 622
27 GitHub Training Kit : in order to make online workbook by sjkim comphy file 졸리운_곰 2016.05.17 270
26 예제를 통해 쉽게 읽어보는 XE모듈개발 시작하기 졸리운_곰 2016.05.10 585
25 node-Tor : JavaScript (node.js) On Tor network file 졸리운_곰 2016.05.04 450
24 [시니시즘] 비회원에게 파일 다운로드 권한이 없다고 뜰 때 졸리운_곰 2016.04.14 766
대표 김성준 주소 : 경기 용인 분당수지 U타워 등록번호 : 142-07-27414
통신판매업 신고 : 제2012-용인수지-0185호 출판업 신고 : 수지구청 제 123호 개인정보보호최고책임자 : 김성준 sjkim70@stechstar.com
대표전화 : 010-4589-2193 [fax] 02-6280-1294 COPYRIGHT(C) stechstar.com ALL RIGHTS RESERVED