Nodejs 入门

安装

Hello World

const http = require('http');

const hostname = '127.0.0.1';
const port = 3000;

const server = http.createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end('Hello World\n');
});

server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});

SailsJs

项目是代码以及各种配置,二进制文件等的组合,是部署的基本单位。我们不需要自己设计项目结构,可以使用脚手架。sailsjs提供了脚手架,以及数据ORM框架。

Sailsjs 官网: http://sailsjs.org/

Sailsjs ORM框架名称为waterline,参考: https://github.com/balderdashy/waterline-docs

一般简单的数据增删改查不需要手工添加任何服务端代码,可以通过脚手架的命令自动产生。

1.安装sailsjs

$ npm install sails -g

2.开始一个新项目

$ sails new hello-world
info: Created a new Sails app `hello-world`!

3.运行

$ cd hello-world
$ sails lift

info: Starting app...

info:
info:
info:    Sails              <|
info:    v0.10.5             |\
info:                       /|.\
info:                      / || \
info:                    ,'  |'  \
info:                 .-'.-==|/_--'
info:                 `--'-------'
info:    __---___--___---___--___---___--___
info:  ____---___--___---___--___---___--___-__
info:
info: Server lifted in `/Users/gongxiancao/project/tmp/hello-world`
info: To see your app, visit http://localhost:1337
info: To shut down Sails, press <CTRL> + C at any time.

debug: --------------------------------------------------------
debug: :: Tue Jul 26 2016 15:42:49 GMT+0800 (CST)

debug: Environment : development
debug: Port        : 1337
debug: --------------------------------------------------------

如果你遇到提示:

 1. safe  - never auto-migrate my database(s). I will do it myself (by hand)
 2. alter - auto-migrate, but attempt to keep my existing data (experimental)
 3. drop  - wipe/drop ALL my data and rebuild models every time I lift Sails

修改 config/models.js:

migrate: 'safe'

4.打开浏览器,浏览网址 http://localhost:1337

5.修改页面内容 views/homepage.ejs:

<div>
    Hello World!
</div>

刷新浏览器页面,会看到“Hello World”字样。

6.添加数据支持

$ sails generate api user
info: Created a new api!

7.打开浏览器访问 http://localhost:1337/user 页面会显示"[]",这实际上是添加了一个api。当提交POST请求到该地址时,该api也能创建user。

8.打开views/homepage.ejs,输入下面的内容并保存

<div>
    输入你的名字
    <form method="POST" action="/user">
        <label>Name:</label>
        <input type="text" name="name">
        <input type="submit" title="提交">
    </form>
</div>

在浏览器里打开 http://localhost:1337 随意输入一个名字,点击“提交”,会看到页面地址变成了 http://localhost:1337 ,而页面内容为:

{
  "name": "gongxian",
  "createdAt": "2016-07-26T08:00:34.997Z",
  "updatedAt": "2016-07-26T08:00:34.997Z",
  "id": 1
}

9.再次访问 http://localhost:1337/user ,可以看到

[
  {
    "name": "gongxian",
    "createdAt": "2016-07-26T08:00:34.997Z",
    "updatedAt": "2016-07-26T08:00:34.997Z",
    "id": 1
  }
]

10.添加前端页面,首先安装前端包管理工具bower

$ npm install bower -g
$ bower init

11.添加Angularjs支持 关于Angularjs: https://angularjs.org/

$ bower install angular --save
$ bower install angular-resource --save
$ bower install angular-ui-router --save

12.修改编译选项,使前端资源包含到html中: tasks/config/copy.js

	grunt.config.set('copy', {
		dev: {
			files: [{
				expand: true,
				cwd: './assets',
				src: ['**/*.!(coffee|less)'],
				dest: '.tmp/public'
			}, {
				expand: true,
				cwd: './bower_components',
				src: [
					'angular/angular.js',
					'angular-resource/angular-resource.js',
					'angular-ui-router/release/angular-ui-router.js'
				],
				dest: '.tmp/public/js/dependencies'
			}]
		},
		build: {
			files: [{
				expand: true,
				cwd: '.tmp/public',
				src: ['**/*'],
				dest: 'www'
			}]
		}
	});

tasks/pipeline.js

var jsFilesToInject = [

  // Load sails.io before everything else
  'js/dependencies/sails.io.js',

  // Dependencies like jQuery, or Angular are brought in here
  'js/dependencies/angular/**/*.js',
  'js/dependencies/**/*.js',

  // All of the rest of your client-side js files
  // will be injected here in no particular order.
  'js/**/*.js'
];

13.按照angular的方式添加必要的tag: views/layout.ejs:

<!DOCTYPE html>
<html ng-app="helloWorld">

views/homepage.ejs:

<div ui-view></div>

14.添加前端js代码

$ mkdir assets/js/app
$ mkdir assets/js/app/user
$ mkdir assets/js/common
$ mkdir assets/js/common/user

15.添加文件 assets/js/app/app.js:

angular.module('helloWorld', [
  'ui.router',
  'helloWorld.user'
])
.config(function ($locationProvider, $urlRouterProvider, $stateProvider) {
  $urlRouterProvider
    .otherwise('/');

  $urlRouterProvider.rule(function ($injector, $location) {

    var path = $location.path();
    var hasTrailingSlash = path[path.length - 1] === '/';

    if (hasTrailingSlash) {

      //if last charcter is a slash, return the same url without the slash
      var newPath = path.substr(0, path.length - 1);
      return newPath;
    }
  });
});

aasets/js/app/user/user.js:

angular.module('helloWorld.user', ['helloWorld.common.user'])
.controller('UserListCtrl', function ($scope, User) {
  $scope.users = User.query();
});

assets/js/app/user/user.route.js:

angular.module('helloWorld.user')
  .config(function ($stateProvider) {
    $stateProvider
      .state('user', {
        url: '/user',
        templateUrl: 'templates/user-list.tpl.html',
        controller: 'UserListCtrl'
      });
  });

assets/js/common/user/user.js:

angular.module('helloWorld.common.user', ['ngResource'])
  .factory('User', ['$resource', function ($resource) {
    return $resource(
      'user/:id',
      {id: '@_id'},
      {
        'update': { method:'PUT' }
      }
    );
  }]);

assets/templates/user-list.tpl.html:

<div>
  <div ng-repeat="user in users">
    {{user.name}}
  </div>
</div>

16.重新执行 sails lift,打开浏览器:http://localhost:1337/#/user 会看到之前添加的用户名称“gongxian”

$ git clone https://github.com/gongxiancao/hello-world.git
$ cd hello-world
$ npm install
$ bower install
$ sails lift

可能遇到的需求及解决方案

典型的需求一般都有现成的第三方nodejs/npm包。下面是常见需求及npm包(可以到 https://www.npmjs.com 搜索)

  • 数组操作常用函数库: lodash, underscore

  • 多异步操作完成等待: bluebird, q, async

  • 加盐hash用户密码: bcrypt, bcryptjs

  • 用户认证: passport, passport-http-bearer, passport-http, passport-local

  • 生成token: jwt-simple

  • 二维码: qr-image

  • 数据库: sails-mongo, mongoose, mongodb

  • 任务队列: kue

  • 缓存: cache-manager

  • 文件存储: gridfs-stream

  • 单元测试: mocha

  • Date格式化: moment

  • 发邮件: nodemailer

  • 定时任务: cron

  • benchmark: debug

  • 调用其他服务器的Api: request

  • 微服务: seneca, seneca-amqp-transport

  • 产品环境运行监控: pm2, forever

其他问题

修改 config/blueprints.js

prefix: '/api',

修改assets/js/common/user/user.js:

angular.module('helloWorld.common.user', ['ngResource'])
  .factory('User', ['$resource', function ($resource) {
    return $resource(
      'api/user/:id',
      {id: '@_id'},
      {
        'update': { method:'PUT' }
      }
    );
  }]);
  • 如何添加自定义api?

修改 api/controllers/UserController.js

module.exports = {
  find: function (req, res) {
    res.json([1]);
  }
};
  • 不想使用自动生成的Api,因为那样等于把数据库直接暴露了,怎么禁用?

修改 config/blueprints.js

actions: false,
rest: false,

修改 config/routes.js

'get /api/user': 'UserController.find'
  • 典型的Api代码是怎样的?

module.exports = {
  find: function (req, res) {
    User.find({})
      .then(function (users) {
        res.json(users);
      })
      .catch(function (err) {
        res.status(400).json({errmsg: 'db error', errcode: 1000});
      });
  }
};
  • 如何实现分页?

有服务端分页和客户端分页,这里只讨论服务端分页。

module.exports = {
  find: function (req, res) {
    var page = parseInt(req.query.page) || 1;
    var perPage = parseInt(req.query.perPage) || 10;
    User.count({})
      .then(function (count) {
        return [
          count,
          User.find({})
            .skip(perPage * (page - 1))
            .limit(perPage)
        ];
      })
      .spread(function (count, users) {
        res.set('x-total', count);
        res.json(users);
      })
      .catch(function (err) {
        res.status(400).json({errmsg: 'db error', errcode: 1000});
      });

  }
};

在前端

$scope.users = User.query({page: 1, perPage: 10}, function (result, headers) {
    $scope.total = parseInt(headers('x-total'));
  });
  • 怎么调试?

推荐使用Visual Studio Code