一个基于 Koa2 构建的类似于 Rails 的 nodejs 龙8国际项目

Ruby on Rails Webpack 中间件   2016-09-06 11:32:13 发布
您的评价:
     
0.0
收藏     2收藏
文件夹
标签
(多个标签用逗号分隔)
最近研究了下Koa2框架,喜爱其中间件的思想。但是发现实在是太简洁了,只有基本功能,虽然可以方便搭各种服务,但是离可以适应快速开发的网站框架还是有点距离。于是参考Rails的大致框架搭建了个网站框架kails, 配合postgres和redis, 实现了MVC架构,前端webpack,react前后端同构等网站开发基本框架。本文主要介绍kails搭建中的各种技术栈和思想。 koa来源于express的主创团队,主要利用es6的generators特性实现了基于中间件思想的新的框架,但是和express不同,koa并不想express一样提供一个可以满足基本网站开发的框架,而更像是一个基本功能模块,要满足网站还是需要自己引入很多功能模块。所以根据选型大的不同,有各种迥异的koa项目,kails由名字也可以看出是一个类似Ruby on Rails的koa项目。 项目地址: https://github.com/embbnux/kails 欢迎Pull Request 主要目录结构如下: ├── app.js ├── assets │ ├── images │ ├── javascripts │ └── stylesheets ├── config │ ├── config.js │ ├── development.js │ ├── test.js │ ├── production.js │ └── webpack.config.js │ ├── webpack ├── routes ├── models ├── controllers ├── views ├── db │ └── migrations ├── helpers ├── index.js ├── package.json ├── public └── test

一、第一步es6支持

kails选用的是koa2作为核心框架,koa2使用es7的async和await等功能,node在开启harmony后还是不能运行,所以要使用babel等语言转化工具进行支持: babel6配置文件: .babelrc: { "presets": [ "es2015", "stage-0", "react" ] } 在入口使用babel加载整个功能,使支持es6 require('babel-core/register') require('babel-polyfill') require('./app.js')

二、核心文件app.js

app.js是核心文件,koa2的中间件的引入和使用主要在这里,这里会引入各种中间件和配置, 具体龙8娱乐官网功能介绍后面会慢慢涉及到。 下面是部分龙8国际娱乐官方老虎机,具体龙8国际娱乐官方老虎机见github上仓库 import Koa from 'koa' import session from 'koa-generic-session' import csrf from 'koa-csrf' import views from 'koa-views' import convert from 'koa-convert' import json from 'koa-json' import bodyParser from 'koa-bodyparser' import config from './config/config' import router from './routes/index' import koaRedis from 'koa-redis' import models from './models/index' const redisStore = koaRedis({ url: config.redisUrl }) const app = new Koa() app.keys = [config.secretKeyBase] app.use(convert(session({ store: redisStore, prefix: 'kails:sess:', key: 'kails.sid' }))) app.use(bodyParser()) app.use(convert(json())) app.use(convert(logger())) // not serve static when deploy if(config.serveStatic){ app.use(convert(require('koa-static')(__dirname + '/public'))) } //views with pug app.use(views('./views', { extension: 'pug' })) // csrf app.use(convert(csrf())) app.use(router.routes(), router.allowedMethods()) app.listen(config.port) export default app

三、MVC框架搭建

网站架构还是以mvc分层多见和实用,能满足很多场景的网站开发了,逻辑再复杂点可以再加个服务层,这里基于koa-router进行路由的分发,从而实行MVC分层 路由的配置主要由routes/index.js文件去自动加载其目录下的其它文件,每个文件负责相应的路由头下的路由分发,如下 routes/index.js import fs from 'fs' import path from 'path' import Router from 'koa-router' const basename = path.basename(module.filename) const router = Router() fs .readdirSync(__dirname) .filter(function(file) { return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js') }) .forEach(function(file) { let route = require(path.join(__dirname, file)) router.use(route.routes(), route.allowedMethods()) }) export default router 路由文件主要负责把相应的请求分发到对应controller中,路由主要采用restful分格。 routes/articles.js import Router from 'koa-router' import articles from '../controllers/articles' const router = Router({ prefix: '/articles' }) router.get('/new', articles.checkLogin, articles.newArticle) router.get('/:id', articles.show) router.put('/:id', articles.checkLogin, articles.checkArticleOwner, articles.checkParamsBody, articles.update) router.get('/:id/edit', articles.checkLogin, articles.checkArticleOwner, articles.edit) router.post('/', articles.checkLogin, articles.checkParamsBody, articles.create) // for require auto in index.js module.exports = router model层这里基于Sequelize实现orm对接底层数据库postgres, 利用sequelize-cli实现数据库的迁移功能. 例子: user.js import bcrypt from 'bcrypt' export default function(sequelize, DataTypes) { const User = sequelize.define('User', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, name: { type: DataTypes.STRING, validate: { notEmpty: true, len: [1, 50] } }, email: { type: DataTypes.STRING, validate: { notEmpty: true, isEmail: true } }, passwordDigest: { type: DataTypes.STRING, field: 'password_digest', validate: { notEmpty: true, len: [8, 128] } }, password: { type: DataTypes.VIRTUAL, allowNull: false, validate: { notEmpty: true } }, passwordConfirmation: { type: DataTypes.VIRTUAL } },{ underscored: true, tableName: 'users', indexes: [{ unique: true, fields: ['email'] }], classMethods: { associate: function(models) { User.hasMany(models.Article, { foreignKey: 'user_id' }) } }, instanceMethods: { authenticate: function(value) { if (bcrypt.compareSync(value, this.passwordDigest)){ return this } else{ return false } } } }) function hasSecurePassword(user, options, callback) { if (user.password != user.passwordConfirmation) { throw new Error('Password confirmation doesn\'t match Password') } bcrypt.hash(user.get('password'), 10, function(err, hash) { if (err) return callback(err) user.set('passwordDigest', hash) return callback(null, options) }) } User.beforeCreate(function(user, options, callback) { user.email = user.email.toLowerCase() if (user.password){ hasSecurePassword(user, options, callback) } else{ return callback(null, options) } }) User.beforeUpdate(function(user, options, callback) { user.email = user.email.toLowerCase() if (user.password){ hasSecurePassword(user, options, callback) } else{ return callback(null, options) } }) return User }

四、开发、测试与线上环境

网站开发测试与部署等都会有不同的环境,也就需要不同的配置,这里我主要分了development,test和production环境,使用时用自动基于NODE_ENV变量加载不同的环境配置。 实现代码: config/config.js var _ = require('lodash'); var development = require('./development'); var test = require('./test'); var production = require('./production'); var env = process.env.NODE_ENV || 'development'; var configs = { development: development, test: test, production: production }; var defaultConfig = { env: env }; var config = _.merge(defaultConfig, configs[env]); module.exports = config; 生产环境的配置: config/production.js const port = Number.parseInt(process.env.PORT, 10) || 5000 module.exports = { port: port, hostName: process.env.HOST_NAME_PRO, serveStatic: process.env.SERVE_STATIC_PRO || false, assetHost: process.env.ASSET_HOST_PRO, redisUrl: process.env.REDIS_URL_PRO, secretKeyBase: process.env.SECRET_KEY_BASE };

五、利用中间件优化代码

koa是以中间件思想构建的,自然代码中离不开中间件,这里介绍几个中间件的应用 currentUser的注入: currentUser用于获取当前登录用户,在网站用户系统上中具有重要的重要 app.use(async (ctx, next) => { let currentUser = null if(ctx.session.userId){ currentUser = await models.User.findById(ctx.session.userId) } ctx.state = { currentUser: currentUser, isUserSignIn: (currentUser != null) } await next() }) 这样在以后的中间件中就可以通过ctx.state.currentUser得到当前用户 优化controller代码 比如article的controller里的edit和update,都需要找到当前的article对象,也需要验证权限,而且是一样的,为了避免代码重复,这里也可以用中间件 controllers/articles.js async function edit(ctx, next) { const locals = { title: '编辑', nav: 'article' } await ctx.render('articles/edit', locals) } async function update(ctx, next) { let article = ctx.state.article article = await article.update(ctx.state.articleParams) ctx.redirect('/articles/' + article.id) return } async function checkLogin(ctx, next) { if(!ctx.state.isUserSignIn){ ctx.status = 302 ctx.redirect('/') return } await next() } async function checkArticleOwner(ctx, next) { const currentUser = ctx.state.currentUser const article = await models.Article.findOne({ where: { id: ctx.params.id, userId: currentUser.id } }) if(article == null){ ctx.redirect('/') return } ctx.state.article = article await next() } 在路由中应用中间件 router.put('/:id', articles.checkLogin, articles.checkArticleOwner, articles.update) router.get('/:id/edit', articles.checkLogin, articles.checkArticleOwner, articles.edit) 这样就相当于实现了rails的before_action的功能

六、webpack配置静态资源

在没实现前后端分离前,工程代码中肯定还是少不了前端代码,现在在webpack是前端模块化编程比较出名的工具,这里用它来做rails中assets pipeline的功能,这里介绍下基本的配置。 config/webpack/base.js var webpack = require('webpack'); var path = require('path'); var publicPath = path.resolve(__dirname, '../', '../', 'public', 'assets'); var ManifestPlugin = require('webpack-manifest-plugin'); var assetHost = require('../config').assetHost; var ExtractTextPlugin = require('extract-text-webpack-plugin'); module.exports = { context: path.resolve(__dirname, '../', '../'), entry: { application: './assets/javascripts/application.js', articles: './assets/javascripts/articles.js', editor: './assets/javascripts/editor.js' }, module: { loaders: [{ test: /\.jsx?$/, exclude: /node_modules/, loader: ['babel-loader'], query: { presets: ['react', 'es2015'] } },{ test: /\.coffee$/, exclude: /node_modules/, loader: 'coffee-loader' }, { test: /\.(woff|woff2|eot|ttf|otf)\??.*$/, loader: 'url-loader?limit=8192&name=[name].[ext]' }, { test: /\.(jpe?g|png|gif|svg)\??.*$/, loader: 'url-loader?limit=8192&name=[name].[ext]' }, { test: /\.css$/, loader: ExtractTextPlugin.extract("style-loader", "css-loader") }, { test: /\.scss$/, loader: ExtractTextPlugin.extract('style', 'css!sass') }] }, resolve: { extensions: ['', '.js', '.jsx', '.coffee', '.json'] }, output: { path: publicPath, publicPath: assetHost + '/assets/', filename: '[name]_bundle.js' }, plugins: [ new webpack.ProvidePlugin({ $: 'jquery', jQuery: 'jquery' }), // new webpack.HotModuleReplacementPlugin(), new ManifestPlugin({ fileName: 'kails_manifest.json' }) ] };

七、react前后端同构

node的好处是v8引擎只要是js就可以跑,所以想react的渲染dom功能也可以在后端渲染,有利用实现react的前后端同构,利于seo,对用户首屏龙8国际娱乐官方老虎机也更加友好。 在前端跑react我就不说了,这里讲下在koa里面怎么实现的: import React from 'react' import { renderToString } from 'react-dom/server' async function index(ctx, next) { const prerenderHtml = await renderToString( <Articles articles={ articles } /> ) }

八、测试与lint

测试和lint自然是开发过程中工程化不可缺少的一部分,这里kails的测试采用mocha,lint使用eslint .eslintrc: { "parser": "babel-eslint", "root": true, "rules": { "new-cap": 0, "strict": 0, "no-underscore-dangle": 0, "no-use-before-define": 1, "eol-last": 1, "indent": [2, 2, { "SwitchCase": 0 }], "quotes": [2, "single"], "linebreak-style": [2, "unix"], "semi": [1, "never"], "no-console": 1, "no-unused-vars": [1, { "argsIgnorePattern": "_", "varsIgnorePattern": "^debug$|^assert$|^withTransaction$" }] }, "env": { "browser": true, "es6": true, "node": true, "mocha": true }, "extends": "eslint:recommended" }

九、console

用过rails的,应该都知道rails有个rails console,可以已命令行的形式进入网站的环境,很是方便,这里基于repl实现: if (process.argv[2] && process.argv[2][0] == 'c') { const repl = require('repl') global.models = models repl.start({ prompt: '> ', useGlobal: true }).on('exit', () => { process.exit() }) } else { app.listen(config.port) }

十、pm2部署

开发完自然是要部署到线上,这里用pm2来管理: NODE_ENV=production ./node_modules/.bin/pm2 start index.js -i 2 --name "kails" --max-memory-restart 300M --merge-logs --log-date-format="YYYY-MM-DD HH:mm Z" --output="log/production.log"

十一、npm scripts

有些常用命令参数较多,也比较长,可以使用npm scripts里为这些命令做一些别名 { "scripts": { "console": "node index.js console", "start": "./node_modules/.bin/nodemon index.js & node_modules/.bin/webpack --config config/webpack.config.js --progress --colors --watch", "app": "node index.js", "pm2": "NODE_ENV=production ./node_modules/.bin/pm2 start index.js -i 2 --name \"kails\" --max-memory-restart 300M --merge-logs --log-date-format=\"YYYY-MM-DD HH:mm Z\" --output=\"log/production.log\"", "pm2:restart": "NODE_ENV=production ./node_modules/.bin/pm2 restart \"kails\"", "pm2:stop": "NODE_ENV=production ./node_modules/.bin/pm2 stop \"kails\"", "pm2:monit": "NODE_ENV=production ./node_modules/.bin/pm2 monit \"kails\"", "pm2:logs": "NODE_ENV=production ./node_modules/.bin/pm2 logs \"kails\"", "test": "NODE_ENV=test ./node_modules/.bin/mocha --compilers js:babel-core/register --recursive --harmony --require babel-polyfill", "assets_build": "node_modules/.bin/webpack --config config/webpack.config.js", "assets_compile": "NODE_ENV=production node_modules/.bin/webpack --config config/webpack.config.js -p", "webpack_dev": "node_modules/.bin/webpack --config config/webpack.config.js --progress --colors --watch", "lint": "eslint . --ext .js", "db:migrate": "node_modules/.bin/sequelize db:migrate", "db:rollback": "node_modules/.bin/sequelize db:migrate:undo", "create:migration": "node_modules/.bin/sequelize migration:create" } } 这样就会多出这些命令: npm install npm run db:migrate NODE_ENV=test npm run db:migrate # run for development, it start app and webpack dev server npm run start # run the app npm run app # run the lint npm run lint # run test npm run test # deploy npm run assets_compile NODE_ENV=production npm run db:migrate npm run pm2

十二、更进一步

现在目前能想到的
  • 性能优化,加快响应速度
  • Dockerfile简化部署
  • 线上代码预编译
  • 更加完善的测试
  来自:https://www.embbnux.com/2016/09/04/kails_with_koa2_like_ruby_on_rails/  

扩展阅读

前端开发指南:汇集主流学习资源
一个基于 Koa2 构建的类似于 Rails 的 nodejs 龙8国际项目
免费编程书籍集合
免费的编程中文书籍索引
基于云的持续集成项目:Travis CI

为您推荐

免费英文编程电子书集合
C/C++ 框架,类库,资源集合
.NET 的 WebSocket 开发包比较
HTML编辑器的CKeditor配置使用方法
机器学习龙8国际项目、类库、软件集合

更多

Ruby on Rails
Webpack
中间件
Node.js 开发
相关文档  — 更多
相关经验  — 更多
相关讨论  — 更多