Brian F爱
Learn from a Google Developer Expert focused on Angular, Web Technologies, and Node.js from Portland, OR.
Ad ·ultimatecourses.com
用终极课程学习角度正确的方式

TypeScript 2 + Express + Mongoose + Mocha + Chai

MongoDB is an excellent choice for persisting data for your web application that uses the Express engine on Node.js.

事实上,Express has documentation on getting started with MongoDB。I recommend you check that out as well.

I love JavaScript, including plain 'ole vanilla JS. But, I also love TypeScript. :)

在这篇文章中,我将向您展示如何在快递Web应用程序中使用MongoDB和Mongoose开始。我将使用类型签字2,这是一个javascript的超集。我还将使用Chai断言使用Mocha测试框架,为猫鼬的接口,模式和型号创建单元测试。

Let's dive in.

源代码

您可以在Github上沿源代码下载并遵循或叉叉以下内容:

Project Structure

I am going to be extending myTypeScript 2 + Express Starter Kit on GitHub。如果您刚刚入门TypeScript 2开发Express Web应用程序,我建议您退房我的文章签署了键字2并在node.js上迈出

Here is what my project structure looks like:

。├── bin │ └── www ├── dist │ ├── interfaces │ │ └── user.js │ ├── models │ │ ├── model.js │ │ └── user.js │ ├── routes │ │ ├── index.js │ │ └── route.js │ ├── schemas │ │ └── user.js │ ├── server.js │ ├── test │ │ └── user.js │ └── views │ └── index.pug ├── gruntfile.js ├── package.json ├── public ├── src │ ├── interfaces │ │ └── user.ts │ ├── models │ │ ├── model.ts │ │ └── user.ts │ ├── npm-debug.log │ ├── routes │ │ ├── index.ts │ │ └── route.ts │ ├── schemas │ │ └── user.ts │ ├── server.ts │ └── test │ └── user.ts └── views └── index.pug

安装MongoDB和Mongoose

OK, let's install MongoDB. I am going to be usingHomebrew:

$ brew update $ brew安装mongodb $LN.-sfv /usr/local/opt/mongodb/*.plist ~/Library/LaunchAgents

The last line will copy thehomebrew.mxcl.mongodb.plistfile so that MongoDB is started when you login.

Now, let's install mongoose. I will be using the--saveflag to write the dependency into my project's包。json文件。

$npm安装猫鼬--save $npm安装@types/mongoose --save-dev $npm安装@types/mongodb --save-dev

Interface

Next, create a new interface for your collection. In this tutorial I am going to be using a collection named采用rs。因此,我打算创造一个新的IUserinterface.

创建一个新文件src / interfaces / user.ts:

exportinterfaceIUser{电子邮件?:string;firstName?:string;lastName?:string;}

Schema

Now, create a new schema for a user called采用rSchema。创建一个新文件src / schemas / user.ts:

import{Schema}from“猫鼬”;exportvar采用rSchema:Schema=Schema({createdAt:Date,电子邮件:String,firstName:String,lastName:String,});采用rSchemapre('save',功能(next){如果(!thiscreatedAt){thiscreatedAt=Date();}next();});

Some things to note:

  • First, I import theSchema来自猫鼬包的课程。
  • Next I create a new instance ofSchema并称之为采用rSchema。请注意,我将此对象从此包导出。
  • I am then creating a pre-save hook (ormiddleware)设置值的值createdAt财产到当前时间。

Model

接下来,我们将为我们的型号创建两个新的接口:

  1. IUserModel将作为模型界面采用rscollection.
  2. imwill serve as the interface for all models in our application.

创建一个新文件src / models / user.ts:

import{文件}from“猫鼬”;import{IUser}from'../interfaces/user';exportinterfaceIUserModelextendsIUser,文件{// Model的自定义方法将在此处定义}
  • First, I am importing the文件从猫鼬包中的界面。
  • I then import theIUserinterface we created.
  • 然后我定义了一个名为的新接口IUserModel。It extends both theIUserinterface we created as well as the文件猫鼬提供的界面。

创建一个新文件src / models / model.ts:

import{Model}from“猫鼬”;import{IUserModel}from'./user';exportinterfaceim{采用r:Model<IUserModel>;}

For right now my application only has a single model named采用rthat is of type型号。That means that the user property isModelof typeIUserModel。We will use this model to create/edit/delete documents within our采用rscollection.

安装q(可选)

我要去使用Q对于猫鼬的承诺图书馆。用来qlibrary we need to first install it via npm:

npm安装q --save

在下一节中,我将用猫鼬用它连接。

Update服务器

OK, we have setup all of the interfaces for our schema and model. We are now ready to import these into our服务器class.

Here is my updatedSRC./server.tsfile:

import*asbodyParserfrom“Body-Parser”;import*ascookieparser.from"cookie-parser";import*asexpressfrom"express";import*asloggerfrom“摩根”;import*aspathfrom“路径”;import呃orHandler=require(“ErrorHandler”);importmethodOverride=require("method-override");import猫鼬=require("mongoose");//import mongoose//routesimport{IndexRoute.}from"./routes/index";//interfacesimport{IUser}from"./interfaces/user";//import IUser//modelsimport{im}from"./models/model";//import IModelimport{IUserModel}from"./models/user";//导入iusermodel.//schemasimport{采用rSchema}from“./schemas/user”;//import userSchema/** * The server. * * @class Server */exportclass服务器{public应用程序:express应用;private模型:im;//an instance of IModel/** * Bootstrap the application. * * @class Server * @method bootstrap * @static * @return {ng.auto.IInjectorService} Returns the newly created injector for this app. */publicstaticbootstrap():服务器{返回服务器();}/** * Constructor. * * @class Server * @constructor */constructor(){//instance defaultsthis模型=Object();//将其初始化为空对象//create expressjs applicationthis应用程序=express();//配置应用程序thisconfig();//添加路由thisroutes();//添加API.thisAPI.();}/** * Create REST API routes * * @class Server * @method api */publicAPI.(){//现在空了}/ ** *配置应用程序* * @class服务器* @method配置* /publicconfig(){constMONGODB_CONNECTION:string="mongodb://localhost:27017/heros";//add static pathsthis应用程序采用(expressstatic(path加入(__dirname.,“上市”)));//configure pugthis应用程序set("views",path加入(__dirname.,"views"));this应用程序set("view engine","pug");//mount loggerthis应用程序采用(logger("dev"));// Mount Json表单解析器this应用程序采用(bodyParserjson());//mount query string parserthis应用程序采用(bodyParserurlencoded({延长:真正}));//mount cookie parkerthis应用程序采用(cookieparser.("SECRET_GOES_HERE"));//mount overridethis应用程序采用(methodOverride());//use q promises全球Promise=require(“q”)Promise;猫鼬Promise=全球Promise;//connect to mongooseconnection:猫鼬连接=猫鼬CreateConnection.(MONGODB_CONNECTION);//create modelsthis模型采用r=connection模型<IUserModel>("User",采用rSchema);// catch 404 and forward to error handlerthis应用程序采用(功能(:任何,req:expressRequest,res:expressResponse,next:express下一步){状态=404.;next();});//error handlingthis应用程序采用(呃orHandler());}/** * Create and return Router. * * @class Server * @method config * @return void */privateroutes(){router:express路由器;router=express路由器();//IndexRouteIndexRoute.create(router);//use router middlewarethis应用程序采用(router);}}

让我们仔细看看对我们的变化服务器class.

imports

import猫鼬=require("mongoose");//import mongoose//interfacesimport{IUser}from"./interfaces/user";//import IUser//modelsimport{im}from"./models/model";//import IModelimport{IUserModel}from"./models/user";//导入iusermodel.//schemasimport{采用rSchema}from“./schemas/user”;//import userSchema

我们需要做的第一件事就是导入猫鼬包。然后我们导入IUserinterface as well as theimIUserModel我们创建的界面。然后导入采用rSchema,我们将用来创建一个新的模型()在猫鼬。我们将连接到MongoDB的连接和模型config()method.

模型属性

然后我在内部创建一个新的私有财产服务器class called模型。This object will be of typeim, which we defined earlier in theSRC./interfaces/model.ts文件。我把它设置为一个新的Objectin the constructor function.

/** * The server. * * @class Server */exportclass服务器{private模型:im;//an instance of IModel/** * Constructor. * * @class Server * @constructor */constructor(){//instance defaultsthis模型=Object();//将其初始化为空对象//code omitted}}

更新config()

最后我们准备设置连接到你r MongoDB server that we installed locally.

As I stated previously, I am going to use theq猫鼬承诺的承诺图书馆。

/ ** *配置应用程序* * @class服务器* @method配置* /publicconfig(){constMONGODB_CONNECTION:string="mongodb://localhost:27017/heros";//code omitted//use q promises全球Promise=require(“q”)Promise;猫鼬Promise=全球Promise;//connect to mongooseconnection:猫鼬连接=猫鼬CreateConnection.(MONGODB_CONNECTION);//create modelsthis模型采用r=connection模型<IUserModel>("User",采用rSchema);//code omitted}}

A couple of things to note:

  • First, I define a constant for the MongoDB connection string. This is just an example. I would highly recommend that you put this in some sort of configuration object that depends on the environment you are running your server in. This way you can run your server locally and test against a local MongoDB installation. And then when you are ready to go to production you can have your production server(s) hit a production MongoDB installation.
  • 请注意,在我指定的连接字符串the DB name as "heros".
  • Next I import theqpromise library. I am going to use this as the global promise library in my Node.js application as well as for mongoose promises.
  • I then create a new connection via thecreateConnection()method passing in the connection string.
  • Finally I create a new模型()instance specifying the singular name of the collection in MongoDB along with theuserschema.that defines the schema for our采用rscollection.

用摩卡和柴测试

To test our models I will be usingMocha与柴作为断言图书馆。

Mocha is a test framework that is very popular for Node.js development. Chai is an assertion library that includes methods likeexpect()assert()

We could use node'sassert模块,但我更喜欢柴的风格。

Let's go ahead and install Mocha and Chai via npm:

$npm安装mocha --save-dev $npm安装chai --save-dev $npm安装@types/mocha --save-dev $npm安装@ types / chai --save-dev

Next, I am going to modify my包。jsonfile to add a customtest脚本:

{"name":"heros",“描述”:“旅游英雄”,"version":"1.0.0","private":真正,"author":"Brian Love","scripts":{"dev":"NODE_ENV=development nodemon ./bin/www","grunt":"grunt",“测试”:"mocha dist/test","start":“节点./bin/www”},//code omitted}

Executingnpm testwill invoke our tests.

Create Test

NOTE:我已更新此帖子以使用Mocha-TypeScript模块。这利用装饰器在Ringscript中,并且可以更轻松地进行测试。所以,您可能想要跳过下一节使用Mocha-TypeScript创建测试。

使用Mocha和Chai创建了一个新文件src / test / user.ts:

import"mocha";import{IUser}from"../interfaces/user";import{IUserModel}from"../models/user";import{采用rSchema}from“../schemas/user”;//use q promises全球Promise=require(“q”)Promise;//import mongooseimport猫鼬=require("mongoose");//使用q库进行猫鼬承诺猫鼬Promise=全球Promise;//connect to mongoose and create modelconstMONGODB_CONNECTION:string="mongodb://localhost:27017/heros";connection:猫鼬连接=猫鼬CreateConnection.(MONGODB_CONNECTION);varUser:猫鼬Model<IUserModel>=connection模型<IUserModel>("User",采用rSchema);//require chai and use should() assertionschai=require("chai");chaishould();describe("User",功能(){describe("create()",功能(){("should create a new User",功能(){//用户对象采用r:IUser={电子邮件:“foo@bar.com”,firstName:“Brian”,lastName:“爱”};//create user and return promise返回User(采用r)保存()然后(result=>{//verify _id property existsresult_IDshouldexist;//verify emailresult电子邮件shouldequal(采用r电子邮件);//验证FirstName.resultfirstNameshouldequal(采用rfirstName);//验证姓氏resultlastNameshouldequal(采用rlastName);})});});});

There is a bit of ceremony at the top of our test. I'm not going to cover it in detail. I import our necessary modules, classes and functions. I then configure mongoose to use the Q library for promises, create the connection to MongoDB and create theUser模型。

The important part is the test that "should create a new user":

  • First, I create a new采用r是一个的对象IUser。I set the values for the电子邮件,firstNamelastName属性。
  • 接下来,我创建一个新User()passing in the采用r目的。然后我调动保存()保存我刚刚创建的新文档的方法。然后我援引然后()promise object method.
  • 请注意,我返回的结果然后()方法,是一个承诺对象。我正在使用asynchronous promise approach with chai。使用此方法时,我们可以简单地返回一个承诺对象而不是调用done()callback method (the more traditional approach to using mocha).
  • Within the然后()resolution callback function I first verify that the_IDproperty exists in theresult, 哪一个是IUserModel将文档保存到我们的MongoDB后返回的文档采用rscollection.
  • 然后我验证了这个价值电子邮件,firstNamelastNamevalues match that of the采用r目的。

好的,让我们运行测试:

$npm跑咕噜咕噜npmtest

If all goes well then you should see a success message:

Mocha成功信息

Using mocha-typescript [Updated 2016-11-28]

Thank you to Michal who recommended using "a more TypeScript approach" to creating my tests using the projectmocha-typescript

要使用Mocha-TypeScript启动,请通过NPM安装它:

$npm安装mocha-typescript --save-dev

The mocha-typescript module includes several decorators that implement the Mocha interfaces. The first decorator that we use is the@套房实现的装饰员mocha.icontextdefinition.interface. Previously I used Mocha's描述()method:

describe('User',功能(){//测试});

使用mocha-typescript我创建了一个UserTestclass that is decorated with the@套房decorator:

@套房classUserTest{//测试}

我们使用的下一个装饰者是@test实现的装饰员Mocha.ITestDefinitioninterface. Previously I used Mocha's它()method:

describe('User',功能(){('should create a new User',功能(){//code that returns a Promise object});});

Using mocha-typescript we can add methods that are decorated with the@testdecorator:

@套房classUserTest{@test("should create a new User")publiccreate(){//code that returns a Promise object}}

Note that a decorator does not end with a semi-colon. If you are getting compilation error呃or TS1146: Declaration expected然后this is likely due to ending your decorator with a semi-colon.

Here is the complete code for myUserTestclass using mocha-typescript:

import{suite,test}from“mocha-typescript”;import{IUser}from"../interfaces/user";import{IUserModel}from"../models/user";import{采用rSchema}from“../schemas/user”;import猫鼬=require("mongoose");@套房classUserTest{//store test dataprivatedata:IUser;//用户模型publicstaticUser:猫鼬Model<IUserModel>;publicstaticbefore(){//use q promises全球Promise=require(“q”)Promise;//使用q库进行猫鼬承诺猫鼬Promise=全球Promise;//connect to mongoose and create modelconstMONGODB_CONNECTION:string="mongodb://localhost:27017/heros";connection:猫鼬连接=猫鼬CreateConnection.(MONGODB_CONNECTION);UserTestUser=connection模型<IUserModel>("User",采用rSchema);//require chai and use should() assertionschai=require("chai");chaishould();}constructor(){thisdata={电子邮件:“foo@bar.com”,firstName:“Brian”,lastName:“爱”};}@test("should create a new User")publiccreate(){//create user and return promise返回UserTestUser(thisdata)保存()然后(result=>{//verify _id property existsresult_IDshouldexist;//verify emailresult电子邮件shouldequal(thisdata电子邮件);//验证FirstName.resultfirstNameshouldequal(thisdatafirstName);//验证姓氏resultlastNameshouldequal(thisdatalastName);});}}

在我们的更新中src / test / user.ts文件我们现在正在使用mocha-typescript模块和@套房@test()装饰器。

Some things to note:

  • 我删除了导入“Mocha”并用导入的导入语句替换它suitetestdecorators (notice to @-symbol) from the "mocha-typescript" module.
  • I then created theUserTestclass.
  • 我有一个名为的私有变量data这将包含我将用于测试的测试数据。这对于在此示例中的简单实现不是必需的,但是,当您有多个测试时,它是有用的。
  • Previously I had some setup code outside of the描述()功能(to configure promises and to create a connection to Mongodb using mongoose). I think this is a standard approach using vanilla JS to writing tests with Mocha. However, with mocha-typescript we cand define a staticbefore()method that is invoked before our tests are executed. I moved my setup code into this method. I think this is a bit cleaner.
  • 在里面构造函数()功能我设置值data
  • I have acreate()method that is decorated with the@test()decorator.
  • Thecreate()method returns a promise object so that my test is asynchronous.
  • I am doing some simple verifications using chai should assertions to test theresult

The last thing to note is that decorators are still experimental in TypeScript. The TypeScript documentation warns us:

注意装饰器是一个可能在未来版本中改变的实验功能。

To enable this experimental feature we need to set a compiler flag named实验者to真正。在我的示例项目中,我正在使用Grunt来编译类型SRC.目录到distdirectory. I have agruntfile.jsfile that uses the咕噜咕噜咕噜task to compile the TypeScript to ES6 compatible JavaScript. Using grunt-ts we can simply add a flag to theoptions:

ts:{应用程序:{files:[{SRC.:["src/\*\*/\*.ts","!src/.baseDir.ts"],dest:"./dist"}],options:{实验者:真正,模块:“commonjs”,target:"es6",Sourcemap.:false}}}

Note that I have set实验者to真正。现在我们已准备好建立并运行我们的测试:

$npm跑咕噜咕噜npmtest

If you are using theTSC.命令行编译您的键字条然后必须使用实验者flag (along with the ES5 or greater target):

TSC.--target ES5 --experimentalDecorators

mongoclient.

In case you want to test that your connection to MongoDB is working using more of a GUI approach then check outmongoclient.。After you install it you will need to configure a connection to MongoDB on localhost and set the DB to "heros". If you look back at the connection string for MongoDB I used "heros" as the DB name.

Here is a screen shot of Mongoclient showing the document that I created when executing the test:

屏幕截图的mongoclient显示用户收集与从测试创建的用户文档。

源代码

您可以在Github上沿源代码下载并遵循或叉叉以下内容:

本教程的源代码是一个名为的分支1-mongodboff of myTypeScript 2 + Express Starter Kit

Brian F爱

Hi, I'm Brian. I am interested in TypeScript, Angular and Node.js. I'm married to my best friend Bonnie, I live in Portland and I ski (a lot).