上周五写了一篇Daruk文章,里面相关的例子代码只有一个简单的hello world,周末花了点时间写了一个简易的comments web demo,带大家实际感受一下,Daruk编写一个web站点的体验。全部代码可见Daruk源码的example下的02-comments目录:
首先,要编写一个这里我们不使用脚手架工具,从零开始写一个daruk项目要怎么开始呢?$ npm init
先在本地目录初始化安装项目,使用npm init命令初始化package脚本。然后我们使用yarn 安装所需要的依赖:$ yarn add daruk koa-ejs koa-bodyparser koa-favicon sqlite3 typeorm$ yarn add --dev ts-node
ts-node为dev模式下需要的依赖,不需要安装在dependencies目录。
然后我们增加npm script 方便我们调试: { "compileOnSave" : true , "compilerOptions" : { "target" : "es2017" , "module" : "commonjs" , "sourceMap" : true , "outDir" : "./build" , "rootDir" : "./" , "removeComments" : true , "noImplicitAny" : true , "experimentalDecorators" : true , "emitDecoratormetadata" : true }, "exclude" : [ "node_modules" ], "include" : [ "./typings*.ts" ] } import { baseController , Context , get } from 'daruk' ; export default class Index extends baseController { @ get public async index { let { page = 1 , limit = 5 } = ctx . query ; page = page - 1 ; let [ comments , counts ] = await ctx . service . CommentsModel . findAllAndCount ; await ctx . render ; } }"script":{ dev:"TS_NODE_FILES=true NODE_ENV=dev ts-node index.ts"}
这段算是启动代码,在调试的时候指定开发环境变量NODE_ENV为dev,入口未index.ts,因为项目中我们会编写d.ts文件所以加上TS_NODE_FILES变量。然后我们创建目录和文件如下:── controllers│ ├── comments.ts│ └── index.ts├── daruk.config.ts├── database│ └── comments.db├── entity│ └── comments.ts├── glues│ └── connection.ts├── index.ts├── package.json├── public│ └── favicon.png├── services│ └── CommentsModel├── tsconfig.json├── typings│ ├── context.d.ts│ ├── glues.d.ts│ └── service.d.ts├── view│ ├── index.tpl│ └── layout.tpl
我下面来一一介绍一下:首先是tsconfig.json文件,用来定义ts的配置项,简单来说就是:
简单说一下,include要设置一下,自动读取项目里的types下的声明文件,然后就是把装饰器选项打开,experimentalDecorators和emitDecoratormetadata,就可以继续下一步了。其他选项的含义看注释或者官网文档自行了解。
到了这里,基本上是把ts的运行环境和项目初始化搞定了,下面我们开始编写项目:首先创建对应所需要的几个目录:controllers为路由所在目录,文件名就是path,使用装饰器定义子path,比如首页的路由是这么写的:
我们首先引入baseController,然后导出我们的Index Controller类,定义了一个get方法,然后我们从query上获取了page,limit参数,默认值设置一下即可,这里只是demo演示。
然后我们使用ctx.service.CommentsModel这个service类上findAllAndCount方法,把page和limit传入,返回所有评论和评论总数。然后我们渲染index.tpl,传入controller里获取的这些值。非常简单,下面我们就从这个入口文件分别说起,其他的部分是如何设置的,首先是看一下service的那个model类是如何挂载在ctx上的。
我们创建对应的services目录,然后创建子目录CommentsModel目录,然后创建index.ts文件,Daruk都是按照目录路径自动挂载的,这在Daruk里是一种约束和默认配置。然后我们看一下service下的model类如何来定义: import { baseService , Context } from 'daruk' ; import Comments from '../../entity/comments' ; export default class CommentsModel extends baseService { public constructor { super ; } public async findAllAndCount { let connection = await this . ctx . glue . connection ; let comments = await connection . getRepository . findAndCount ; return comments ; } public async insert { let connection = await this . ctx . glue . connection ; let EntityManager = connection . manager ; let comments = await EntityManager . create ; return await EntityManager . save ; } }
首先引入baseService类并继承,然后导出我们的CommentModel类,定义了2个对应的方法,findAllAndCount还有insert方法。
我们先说findAllAndCount方法,我们先定义了2个参数,配置了默认参数,然后我们获取数据库连接,数据库连接保存在ctx.glue.connection上,这个后边再说,glue的写法,我们只需要知道,这里我们拿到了一个数据库连接实例,然后我们使用typeorm的connection的getRepository方法获取Comments表,然后我们执行findAndCount方法,传入对应的skip跳页,take截取数,order排序,然后返回查找结果。
这里都是typeorm的对应方法,不多做介绍,可以去学习相关的typeorm文档知识,然后insert方法就更简单了。我们同样获得数据库连接,这次是EntityManager对象,然后创建一个新的Comment元素对象,传入初始值,name和content,最后调用save方法保存。嗯,正常来说,无论开发什么网站,model层在Daruk里就是这样的写法了,我们尽量不引入太多其他高级概念比如框架装饰器,这次主要说一下各个目录得功能作用。
然后我们看一下glue目录是怎么用的,glue顾名思义,放置是与请求无关的实例类,services在文档里写的很清楚,每次请求都会重新实例化,请求结束会被销毁,适合编写围绕ctx的抽象功能,数据库连接不是每次都建立,所以放置到gule里非常合适。glues目录下我们创建一个connection.ts文件: import { Daruk } from 'daruk' ; import { join } from 'path' ; import { createConnection } from 'typeorm' ; import Comments from '../entity/comments' ; export default async function { const connection = await createConnection , entities : [ Comments ], synchronize : true , logging : process . env . NODE_ENV === 'dev' }); return connection ; } import { Column , Entity , PrimaryGeneratedColumn } from 'typeorm' ; @ Entity class Comments { @ PrimaryGeneratedColumn public comments_id : number ; @ Column public name : string ; @ Column public content : string ; } export default Comments ;
这个文件也比较简单,我们引入Comments model,这是typeorm的概念,代表数据库里的comments表模型,然后我们导出一个函数,函数返回值是typeorm创建的connection,我们的数据库选用的是sqlite3,所以参数都是sqlite3的参数,数据库保存在database目录下。那么通过glue和service,controller大家应该知道如何做关联了。我们下面看一下entity目录下的comment model是怎么定义的:
这里的model都是基于typeorm的api来写的,typeorm是基于ts的一款开源orm,非常适合在ts项目中使用,这里简单来说创建的表结构就是3个column,其中comments_id是一个自增key。
然后我们看一下我们comments.ts这个controller是怎么写的: import { baseController , Context , post } from 'daruk' ; export default class Comments extends baseController { @ post public async index { let { name , content } = ctx . request . body ; await ctx . service . CommentsModel . insert ; ctx . redirect ; await next ; } } import { Daruk } from 'daruk' ; import { join } from 'path' ; interface Middleware { [ key : string ] : => any ; } interface DarukConfig { middlewareOrder : string []; middleware : Middleware ; } export default function { const globalConfig : DarukConfig = { middlewareOrder : [ 'koa-ejs' , 'koa-favicon' , 'koa-bodyparser' ], middleware : { 'koa-favicon' { return mid ; }, // https://github.com/koajs/ejs 'koa-ejs' { mid , viewExt : 'tpl' }); return false ; }, // https://github.com/koajs/bodyparser 'koa-bodyparser' { return mid ; } } }; return globalConfig ; }
这里和index不同的是调用完model的insert方法后,我们使用ctx.redirect调回了首页。这里和koa2的开发方式是一致的,拿到的ctx就是koa里的ctx。基本上到这里已经快结束了,最后我说一下view部分的设置是如何实现的,要说这个部分,就要看一下这个文件,daruk.config.ts,这个文件可以用可编程的方式来描述Daruk的gule,middleware,utils,service等等的一个配置文件,如果存在,它是会被自动读取的,不多说直接看下内容:
这个daruk.config.ts文件导出一个函数,函数返回值就是对应的配置文件,这里就只用到了2个options,一个是middlewareOrder,用来描述中间件调用顺序的,一个就是middleware对象,用来配置middleware的。从代码中可以看到我们使用了koa-favicon,在对应回调里我们设置了favicon的地址,然后我们配置koa-ejs,定义了一些设置和view模板目录,最后是koa-bodyparser,用来获取post请求上的request.body.通过daruk.config.ts文件,我们把所有的中间件配置和挂载统一到了一个配置项中,非常方便和灵活,当然自己定义的middleware这里没有示例,我之后会专门讲Daruk的middware的用法,这里只是演示全局中间件的配置和调用方法。
最后我们再编写我们的入口启动文件,根目录的index.ts: import { Daruk } from 'daruk' ; const port = 3000 ; const myApp = new Daruk ; myApp . run ;
和helloworld差不多,就不介绍了,debug是控制log开关的,在dev模式下打印的log会更美观。成果就是这个样子的,view层模板就不贴了,不是重要部分,大家可以直接看源代码,就是ejs。这是typeorm上的方法,通过glue.connection拿到的提示,说明类型没有丢失。