官术网_书友最值得收藏!

Using an application framework

The server we created in the REPL used the low-level HTTP module built into Node.js. This provides an API for creating a server that reads data from requests and writes to responses.

As with other programming platforms, there are frameworks available providing more useful high-level abstractions for writing web applications. These include things such as URL routing and templating engines. ASP.NET MVC, Ruby on Rails, and Spring MVC are all examples of such frameworks on different platforms.

Note

Example code

If you get stuck at any point in this book, you can follow along with the code at https://github.com/NodeJsForDevelopers (there is a repository for each chapter and a commit for each heading that introduces any new code).

In this book, we'll be using a framework called Express to write a web application in Node.js. Express is the most popular web application framework for Node.js. It is well suited to small-scale applications such as the one we'll be building. It also provides a good introduction to important concepts. Most other popular Node.js web application frameworks are conceptually similar to Express, and several are actually built on top of it.

Getting started with Express

To get our Express-based application started, we'll use npm to install the express-generator package, which will create a skeleton application based on Express. Run the following command in the console (that is, your regular terminal, not inside the Node.js REPL):

> npm install -g express-generator@~4.x

The -g option installs the Express generator globally, so you can run it from anywhere. The next command we run will create a new folder to contain our application code, so run this command wherever you want this folder to reside:

> express --hogan chapter02

Note

Templating engines

Express offers a choice of templating engines. We'll be using Hogan, which is an implementation of the Mustache templating engine. You may already be familiar with Mustache from client-side libraries. Don't worry if not, though. It's very simple to pick up.

As you can see from the output, this sets up a minimal standard application structure for us. Now run the following command (as instructed by the generator output) to install the modules on which our application depends:

> cd chapter02
> npm install

The generator has created a skeleton Node.js web application for us. Let's try running this:

> npm start

Now visit http://localhost:3000 again and you'll see the Express welcome page as shown here:

Getting started with Express

Exploring our Express application

Let's look at the folders that the Express generator created for us:

  • node_modules: This folder contains the third-party packages that our application depends on, which are installed when we run npm install (it is common to exclude this directory from source control)
  • public: This folder contains the static assets of our application: images, client-side JavaScript, and CSS
  • routes: This folder contains the logic of our application
  • views: This folder contains the server-side templates for our application

There are also some files that aren't contained in any of the preceding folders:

  • package.json: This file contains metadata about our application used by the npm install and npm start commands used earlier. We'll explore this file further in Chapter 4, Introducing Node.js Modules.
  • app.js: This file is the main entry point for our application, which glues together all of the preceding components and initializes Express. We'll go through this file in more detail later on in this chapter.
  • bin/www: This file is a Node.js script that launches our application. This is the script that gets executed when we run npm start.

It's not important to understand everything in the bin/www script at this point. However, note that it uses the same http.createServer call as in the REPL example before. This time, though, the listener argument is not a simple function but is our entire application (defined in app.js).

Understanding Express routes and views

Routes in Express contain the logic for handling requests and rendering the appropriate response. They have similar responsibilities to controllers in MVC frameworks such as ASP.NET, Spring MVC, or Ruby on Rails.

The route that serves the page we just viewed in the browser can be found at routes/index.js and looks like this:

var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
  res.render('index', { title: 'Express' });
});

module.exports = router;

The require call imports the Express module. We will discuss how this works in much more detail in Chapter 4, Introducing Node.js Modules. For now, think of it like a using or import statement in .NET or Java. The call to express.Router() creates a context under which we can define new routes. We will discuss this in more detail later on in this chapter (see Creating modular applications with Express). The router.get() call adds a new handler to this context for GET requests to the path '/'.

The callback function takes a request and response argument, similar to the listener in our "Hello World!" server at the beginning of this chapter. However, the request and response in this case are objects provided by Express, with additional functionality.

The render function allows us to respond with a template, which is rendered using the data we pass to it. This is typically the last thing you will do in a route's callback function. Here, we pass an object containing the title Express to the view template.

The view template can be found at views/index.hjs and looks like this:

<!DOCTYPE html>
<html>
  <head>
    <title>{{ title }}</title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
  </head>
  <body>
    <h1>{{ title }}</h1>
    <p>Welcome to {{ title }}</p>
  </body>
</html>

This is a Hogan template. As mentioned previously, Hogan is an implementation of Mustache, a very lightweight templating language that limits the amount of logic in views. You can see the full syntax of Mustache at https://mustache.github.io/mustache.5.html.

Our template is a simple HTML page with some special template tags. The {{ title }} tags are replaced with the title field from the data passed in by the route.

Let's change the heading in the view to include a name as well as a title. It should look like this:

<h1>Hello, {{ name }}!</h1>

Try reloading the page again. You should see the following:

Understanding Express routes and views

We don't have a name yet. That's because there is no name field in our view data. Let's fix that by editing our route:

var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
 res.render('index', { title: 'Express', name: 'World' });
});

module.exports = router;

If we refresh our browser again at this point, we still won't see the name. That's because our application has already loaded our route, so won't pick up the change.

Go back to your terminal and kill the running application. Start it again (using npm start) and reload the page in the browser. You should now see the text Hello, World!.

Using nodemon for automatic restarts

Restarting the application every time we make a change is a bit tedious. We can do better by running our application with nodemon, which will automatically restart the application whenever we make a change:

> npm install -g nodemon
> nodemon

Try updating the routes/index.js file again (for example, change the name string to your own name), then refresh the browser. This time, the change should appear without you needing to manually stop and restart the application. Note that the process is restarted by nodemon though, so if our application stored any internal state, this would be lost.

Creating modular applications with Express

To find out how our route gets called when a request is made, we need to look at the app.js bootstrapping file. See the following two lines:

var routes = require('./routes/index');
...
app.use('/', routes);

This tells Express to use the routing context defined in routes/index.js for requests to the root path ('/').

There is a similar call setting up a route under the /users path. Try visiting this path in your browser. The route that renders this response is defined in /routes/users.js.

Note that the route in /routes/users.js is also bound to '/', the same as the route in /routes/index.js. The reason this works is that these paths are each relative to a separate Router instance, and the instance created in /routes/users.js is mounted under the /users path in app.js.

This mechanism makes it easy to build large applications composed from smaller modules. You can think of it as similar to the Areas functionality in ASP.NET MVC, or simply as an alternative structure to MVC controllers grouping together action methods.

Bootstrapping an Express application

Let's take a look at the rest of the app.js file. Your file might not look identical to the listings below due to minor differences in our versions of Express, but it will contain broadly the same sections.

The various require() calls at the top of the file import the modules used by the application, including built-in Node.js modules (HTTP and Path), third-party libraries, and the application's own routes. The following lines initialize Express, telling it where to look for view templates and what rendering engine to use (in our case, Hogan):

var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', '{views}');

The rest of the file consists of calls to app.use(). These register various different middleware for processing the request. The order in which they are registered forms a request processing pipeline. You might already be familiar with this pattern from servlet filters in Java, or the IAppBuilder/IApplicationBuilder/IBuilder interfaces in OWIN and ASP.NET. Don't worry if not though; we'll explore middleware thoroughly here.

Understanding Express middleware

Middleware functions are the fundamental building blocks of an Express application. They are simply functions that take request and response arguments (just like our listener functions before) and a reference to the next middleware in the chain.

Each middleware function can manipulate the request and response objects before passing onto the next middleware in the chain. By chaining middleware together in this way, you can build complex functionality from simple modular components. It also allows clean separation between your application logic and cross-cutting concerns such as logging, authentication, or error handling.

Instead of passing control to the next middleware in the chain, a function can also end the processing of the request and return a response. Middleware can also be mounted to specific paths or router instances, for example, if we want enhanced logging on a particular part of our site.

In fact, Express routes are just another example of middleware: the routes that we have already looked at are ordinary middleware functions with the same three arguments noted above. They just happen to be mounted to a specific path and to return a response.

Implementing error handling

Let's take a closer look at some of the middleware in app.js. First, look at the 404 error handler:

app.use(function(req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});

This function always returns a response. So why do we not always get a 404 from our application? Remember that middleware is called in order, and the routes (which are registered before this function) return a response and don't call the next middleware. This means that the 404 function will only be called for requests that don't match any route, which is exactly what we want.

What about the other two error handlers in app.js? They return a 500 response with a custom error page. Why does our application not return a 500 response in all cases? How do these get executed if another middleware throws an error before calling next()?

Error-handling is a special case in Express. Error-handling middleware functions take four arguments instead of three, with the first parameter being an error. They should be registered last, after all other middlewares.

In the case of an error (either an error being thrown or a middleware function passing in an error argument when calling next), Express will skip any other non-error handling middleware and start executing the error handlers.

Using Express middleware

Let's see some Express middleware in action by making use of cookie parsing middleware (which is already part of the skeleton application created by express-generator). We can do this by using a cookie to store how many times someone has visited the site. Update routes/index.js as follows:

router.get('/', function(req, res, next) {
 var visits = parseInt(req.cookies.visits) || 0;
 visits += 1;
 res.cookie('visits', visits);
 res.render('index',
 { title: 'Express', name: 'World', visits: visits }
 );
});

And add a new line to views/index.hjs:

<p>You have visited this site {{visits}} time(s).</p>

Now visit http://localhost:3000/ again and refresh the page a few times. You should see the visit count increase based on the value stored in the cookie. To see what the cookie parsing middleware is doing for us, try deleting or commenting out the following line from app.js and reloading the page:

app.use(cookieParser());

As you can see from the error, the cookies property of the request is now undefined. The cookie parsing middleware looks at the cookie header of the request and turns it into a convenient JavaScript object for us. This is a common use case for middleware. The bodyParser middleware functions do a very similar job with the request body, turning raw text into a JavaScript object that is easier to use in our routes.

Note that the error response above also demonstrates our error handling middleware. Try commenting out the error handlers at the end of the app.js file and reloading the page again. We now get the default stack trace rather than the custom error response defined in our handler.

主站蜘蛛池模板: 十堰市| 虎林市| 巨野县| 炉霍县| 伊宁县| 铜川市| 喜德县| 九龙城区| 靖宇县| 宁强县| 繁昌县| 谷城县| 北流市| 屏南县| 青海省| 灵石县| 云霄县| 青阳县| 固镇县| 横山县| 昭平县| 高淳县| 达孜县| 东乌珠穆沁旗| 休宁县| 广东省| 和田市| 萝北县| 从化市| 陆丰市| 来宾市| 葫芦岛市| 武冈市| 察雅县| 垣曲县| 乌拉特前旗| 北辰区| 讷河市| 乃东县| 秀山| 苏尼特右旗|