MEAN Stack - REST APIs

該文章為網(wǎng)絡(luò)課 Introduction to MongoDB using the MEAN Stack學(xué)習(xí)筆記。

1.關(guān)于REST(Representational State Transfer)

1.1 什么是REST?

是一種編程模式,它通過(guò)HTTP來(lái)搭建客戶端瀏覽器與服務(wù)器之間的通信。
It is a paradigm for a browser with a server over HTTP.

關(guān)于HTTP request,可以看做是三個(gè)方面的組合:

  1. Verb:e.g. GET/POST
  2. Resource:e.g. /home
  3. Body (optional):e.g. JSON

當(dāng)服務(wù)器收到HTTP request之后,會(huì)向客戶端返回HTTP request,這其中可以包含很多信息,比如status,JSON等等。

1.2 三個(gè)基本方面

3 fundamental aspects of REST design pattern: client, server, resource

1.3 CRUD (Creat/Read/Update/Delete)

POST -> Create
GET -> Read

1.4 什么是endpoint/routes?

HTTP中使用的GET/user/42或者POST/user等被稱之為endpoint/routes。創(chuàng)建REST API就是在創(chuàng)建能夠讓客戶端javascript代碼能夠使用的endpoint/routes。

1.5 什么是RESTful ?

  • 如果一個(gè)架構(gòu)符合REST原則,就稱它為RESTful架構(gòu)。

  • 總結(jié)一下什么是RESTful架構(gòu):

(1)每一個(gè)URI代表一種資源; -----> e.g. URLs

(2)客戶端和服務(wù)器之間,傳遞這種資源的某種表現(xiàn)層; -----> e.g. data type

(3)客戶端通過(guò)四個(gè)HTTP動(dòng)詞,對(duì)服務(wù)器端資源進(jìn)行操作,實(shí)現(xiàn)"表現(xiàn)層狀態(tài)轉(zhuǎn)化"。 -----> method: GET/POST/PUT/DELETE

2. Express

2.1 是什么?

是用于啟動(dòng)一個(gè)HTTP Server的Node.js pakage。相當(dāng)于是一個(gè)Web Application Framework允許設(shè)計(jì)者用Node.js來(lái)搭建server。

2.2 一個(gè)簡(jiǎn)單的Hello World例子

  • package.json
  "dependencies": {
    "express": "4.12.3"
  }
  • server.js
var express = require('express');

var app = express(); // 創(chuàng)建一個(gè)express web app server

app.get('/', function(req, res) { // 創(chuàng)建一個(gè)routes為"/"的HTTP GET響應(yīng)
  res.send('Hello, world!'); //返回一個(gè)"Hello, world!"頁(yè)面的響應(yīng)
});
 
// 創(chuàng)建一個(gè)routes為"/user/:user"的HTTP GET響應(yīng)
// :user表示user是一個(gè)可變的參數(shù),可以利用req.params取到
// req.query.school可以取到HTTP中?之后的school對(duì)應(yīng)的內(nèi)容
app.get('/user/:user', function(req, res) { 
  res.send('Page for user ' + req.params.user + + ' with school ' + req.query.school);
});

app.listen(3000); //讓這個(gè)server監(jiān)聽來(lái)自port3000的請(qǐng)求
console.log('Server listening on port 3000!');
  • 結(jié)果
routes "/"
routes "/user/jiayuan"

3. TDD (Test Driven Development)

  • 思想:不需要瀏覽器來(lái)對(duì)server進(jìn)行測(cè)試,利用一個(gè)叫做“superagent”的package來(lái)模擬瀏覽器發(fā)出的HTTP request,并接收HTTP response,然后利用類似常規(guī)mocha測(cè)試的方法進(jìn)行測(cè)試。

  • package.json

{
  "dependencies": {
    "express": "4.12.3",
    "mocha": "2.2.4",
    "superagent": "1.2.0"
  },
  "scripts": {
    "test": "mocha test.js"
  }
}
  • server.js
    值得注意的是,由于此處重視的是測(cè)試,因此server上只寫express的邏輯,并以一個(gè)函數(shù)的方式export出去,此處并不需要寫對(duì)server的啟動(dòng),即不需要寫對(duì)port的監(jiān)聽。
var express = require('express');

module.exports = function() {
  var app = express();

  app.get('/', function(req, res) {
    res.send('Hello, world!');
  });

  app.get('/user/:user', function(req, res) {
    res.send('Page for user ' + req.params.user + ' with option ' +
      req.query.option);
  });

  return app;
};
  • test.js
    對(duì)server的啟動(dòng)以及關(guān)閉由測(cè)試文件來(lái)執(zhí)行。
var app = require('./server');
var assert = require('assert');
var superagent = require('superagent');

describe('server', function() {
  var server;

  beforeEach(function() {
    server = app().listen(3000); //啟動(dòng)server
  });

  afterEach(function() {
    server.close(); //關(guān)閉server
  });

  it('prints out "Hello, world" when user goes to /', function(done) {
    // 創(chuàng)建一個(gè)對(duì)"http://localhost:3000/"的GET請(qǐng)求,并對(duì)響應(yīng)進(jìn)行測(cè)試
    superagent.get('http://localhost:3000/', function(error, res) {
      assert.ifError(error);
      assert.equal(res.status, 200);
      assert.equal(res.text, "Hello, world!");
      done();
    });
  });
});

4. Dependency Injection

  • 思想:相當(dāng)于一種code isolation。對(duì)作用是initialization的代碼與使用這些代碼的代碼進(jìn)行隔離, 以此增加設(shè)計(jì)的靈活性。

  • 例子
    假設(shè)我們需要利用express中從客戶端取到的數(shù)據(jù)對(duì)數(shù)據(jù)庫(kù)進(jìn)行操作,那么一個(gè)非常直觀的做法是:

  1. 把mongoDB initialization setup的代碼放在server.js的全局處,然后在routeHandler中使用從客戶端取到的數(shù)據(jù)對(duì)mongoDB進(jìn)行操作。
//全局scope: mongoDB setup
var mongoose = require('mongoose');

mongoose.connect('mongodb://localhost:27017/test');

var userSchema = new mongoose.Schema({
  name: String
});

var User = mongoose.model('User', userSchema);

//express
var express = require('express');

var app = express();

app.get('/user/:user', function(req, res) {
    //利用id來(lái)查user并以JSON可是作為響應(yīng)
    User.findOne({ _id: req.params.id }, function(error, user) {
       res.json({ user: user });
    });
});


app.listen(3000);
console.log('Server listening on port 3000!');

這樣做的弊端是:當(dāng)需要用不同的數(shù)據(jù)庫(kù)是,我不僅需要改動(dòng)全局scope中數(shù)據(jù)庫(kù)的setup代碼,我還需要改動(dòng)express中的routeHandler代碼。當(dāng)express中邏輯比較復(fù)雜的時(shí)候,這樣的改動(dòng)就顯得很cumbersome。

  1. 利用一個(gè)名叫"wagner-core"的包裹來(lái)對(duì)數(shù)據(jù)庫(kù)setup工作進(jìn)行封裝,寫成“service”。
var express = require('express');
var mongoose = require('mongoose');
var wagner = require('wagner-core');

// Dependency Injection: setupModels 和 setupApp的代碼被隔離開
setupModels(mongoose, wagner);

var app = express();

setupApp(app, wagner);

app.listen(3000);
console.log('Listening on port 3000!');

//對(duì) model的set up不再是全局scope,而是在一個(gè)函數(shù)中的局部scope
function setupModels(mongoose, wagner) {
  mongoose.connect('mongodb://localhost:27017/test');

  var userSchema = new mongoose.Schema({
    name: String
  });
  var User = mongoose.model('User', userSchema);

  wagner.factory('User', function() { // 在wagner中定義以個(gè)名叫“User”的Service
    return User;
  });
}

function setupApp(app, wagner) {
  //雖然setupApp中的代碼與setupModel中的scope并不一樣
  //但利用wagner.invoke來(lái)引入定義了的service,因此setupApp中的代碼可以在這個(gè)scope中得到利用
  var routeHandler = wagner.invoke(function(User) {
    return function(req, res) {
      User.findOne({ _id: req.params.id }, function(error, user) {
        res.json({ user: user });
      });
    };
  });

  app.get('/user/:id', routeHandler);
}

這樣的好處在于:

  • setup的代碼不需要在全局scope也可以在express的routeHandler中得到使用。這是一種closure的概念,User是函數(shù)routeHandler的closure,相當(dāng)于雖然User并不在routeHandler的scope里面,但是卻可以用它,想到與routeHandler closes User。
  • 對(duì)setup代碼的改動(dòng)可以反映到express的代碼中,而express得代碼并不需要?jiǎng)印1热缫獡Q一個(gè)數(shù)據(jù)庫(kù),只需要改動(dòng)setupModel中的代碼。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容