精华 通过 nodeclub 项目源码来讲解如何做一个 nodejs + express + mongodb 项目
发布于 3 年前 作者 6174 41714 次浏览 最后一次编辑是 1 年前

通过 nodeclub 项目源码来讲解如何做一个 nodejs + express + mongodb 项目


##1. About

  • 1.1 what: nodeclub 是 cnodejs.org 的源码,CNode 算是一个基本的博客系统,包含文章发布, 关注,评论等功能。这些功能可以说是任何一个网站的基础。从 nodeclub 里可以学到什么?
    • 1.基本的架构
    • 2.开发测试过程
    • 3.MVC 的设计
    • 4.middleware 的正确用法
    • 5.如何设计 Mongodb schema
    • 6.如何正确的使用 Mongoose
    • 7.如何实现一个标签系统
    • 8.plugins? services ?
    • 9.如何正确的使用 EJS helper
    • 10.到底该怎样写路由, restful?
    • 11.如何做基本的控制验证
    • 12.如何发邮件
    • 13.session
    • 14.GitHub 用户登录
    • 15.图片上传
    • 16.消息发送

除了 nodeclub 源码的学习笔记以外, 还会有一点最近捣鼓这一块的经验分享

  • 1.一个完整的消息订阅设计
  • 2.消息推送, socket + express 如何合作?
  • 3.包装 action
  • 4.蛋疼的异步回调如何处理

nodeclub源码

  • 1.2 why:

对于想用 nodejs + express + mongodb 来做网站技术基础的项目, nodeclub 可以说是很好的源码级指南,当然也是我的指南,这篇文章权当做个人学习 nodeclub 的学习笔记。

  • 1.3 who
who = 一名本应该在写前端的但不知怎的一直在写后端的马脓 -> 
  [@echo](/user/echo) 'github: https://github.com/6174'
  [@echo](/user/echo) 'weibo: http://weibo.com/u/2254313183'
  [@echo](/user/echo) 'email: 57017125[@qq](/user/qq).com'
  [@echo](/user/echo) 'ps: 一直在求后端partner中,有意者联系我' 
  [@send](/user/send)()

2. nodeclub 中用到了哪些开源技术


  • 2.1 Node.js 项目一大优点就是有一个 package.json, 里边的 dependencies & devDependencies 可以看到这个项目所有的依赖。 对于有经验的开发者来说, 看完 package.json 基本就能知道项目的架构是怎样。

  • 2.2 dependencies

  • express: 基础框架:
  • mongodb: 数据存储
  • mongoose: orm
  • connect-mongo: session (对于redis, 可以使用connect-redis)
  • nodemailer:邮件
  • validator:验证
  • passportpassport-github: passport,
  • loader: ejs-view-helper, 静态资源加载处理
  • 其他: event-proxy, node-markdown, ndir
  • 2.3 devDependencies
  • 测试框架:mocha, should
  • 运行: forever
  • 请求模拟: supertest
  • 2.4 nodeclub 以 express + mongodb + mongoose 作为基本框架, 典型的 MVC 应用
  • Model: 对应mongoose orm, models目录
  • view: ejs模板, views目录
  • controler:express middleware , contollers目录
  • 2.5 目录结构:
- common/
- controllers/
- libs/
# express中间件, 基本的auth, session 验证
- middlewares/
- models/
#消息, 邮件服务
- services/
- plugins/
#可以看做是对model处理的加工库
- proxy/
- test/
- views/
- app.js
- route.js
- config.js

3. 应用入口 app.js


神圣的入口文件,几乎每个项目都会有一个 entry,对于了解一个应用熟悉入口逻辑很重要。 下面将分步来看看,nodeclub 的 app.js 做了什么:

3.1 require(./config)

  • 3.1.1 应用相关的配置的设置, 主要分为
  • 1.应用全局数据配置
  • 2.数据库连接配置
  • 3.session,auth 相关配置
  • 4.rss配置
  • 5.mail配置
  • 6.第三方连接相关配置, github, weibo

配置文件也是了解应用的一个好地方, 在 config.default.js 中可以看到以下信息, 这些很可能是我们平时做应用开发的时候没有留意到的地方

//--应用数据统计
google_tracker_id: 'UA-41753901-5',

//--静态文件很可能使用cdn来做
site_static_host: '', // 静态文件存储域名

//--求解释
site_enable_search_preview: false, // 开启google search preview
site_google_search_domain:  'cnodejs.org',  // google search preview中要搜索的域名

//--运营数据
list_topic_count: 20,
post_interval: 10000,
admins: { admin: true },
side_ads:[]
allow_sign_up: true,

//--插件模式
plugins: []
  • 3.1.2 当然这里的配置文件是 default 的,配置文件可以放在一个 config 的文件夹下面,多个文件的方式来整理。比如运营数据配置和其他数据配置分开,因为很有可能需要做一个小的工具来让非技术人员配置相关参数。这时候可以用一个 index.js 作为 facade,相当于一个大的 node module。

3.2 require('./models')

  • 3.2.1 之前已经讲了 models/ 目录对应 MVC 的 M 部分。
  • 3.2.2 models/ 目录下面有 index.js, require('./models') 相当于 require('./models/index')

index 相当于一个模型的 facade, index.js 做得事情分别是

  • 1.connect mongodb
  • 2.require 各个 model 模块
  • 3.exports 所有的 model

简单而言就是初始化了应用 model 层。

  • 3.2.3 模型使用 orm 框架 mogoose 来写,了解 mogoose 过后, models 部分的代码也就是秒懂了

, 我说的只是代码,literaly, 一个项目的核心就是 model 的设计,以前做过的任何项目都是一样, 数据库 table 的设计好坏直接影响应用的开发以及性能。 下面来看看各个 model 的 schema 设计(几乎直接 ctr+c, ctr+v 加上了一点点注释) :

  • 3.2.4 user
var UserSchema = new Schema({
  //--基本用户信息, index表示在mongodb中会建立索引
  //--unique: true 唯一性设置
  name: { type: String, index: true },
  loginname: { type: String, unique: true },
  pass: { type: String },
  email: { type: String, unique: true },
  url: { type: String },
  profile_image_url: {type: String},
  location: { type: String },
  signature: { type: String },
  profile: { type: String },
  weibo: { type: String },
  avatar: { type: String },
  githubId: { type: String, index: true },
  githubUsername: {type: String},
  is_block: {type: Boolean, default: false},
  
  //--用户产生数据meta
  score: { type: Number, default: 0 },
  topic_count: { type: Number, default: 0 },
  reply_count: { type: Number, default: 0 },
  follower_count: { type: Number, default: 0 },
  following_count: { type: Number, default: 0 },
  collect_tag_count: { type: Number, default: 0 },
  collect_topic_count: { type: Number, default: 0 },
  create_at: { type: Date, default: Date.now },
  update_at: { type: Date, default: Date.now },
  is_star: { type: Boolean },
  level: { type: String },
  active: { type: Boolean, default: true },
    
  //-mail
  receive_reply_mail: {type: Boolean, default: false },
  receive_at_mail: { type: Boolean, default: false },
  from_wp: { type: Boolean },
  retrieve_time : {type: Number},
  retrieve_key : {type: String}
});
  • 3.2.5 topic 话题
//1 <- 多
//tag <- topic <- collect  
var TopicSchema = new Schema({
  title: { type: String },
  content: { type: String },
  author_id: { type: ObjectId },
  top: { type: Boolean, default: false },
  reply_count: { type: Number, default: 0 },
  visit_count: { type: Number, default: 0 },
  collect_count: { type: Number, default: 0 },
  create_at: { type: Date, default: Date.now },
  update_at: { type: Date, default: Date.now },
  //--这里reply的设计方式不知道是否合适, 因为mongdb不同于关系型数据库,这里每次读取文章都需要重reply集合里边查找遍历一边,文章是读繁忙的。
  //-- 一个document的大小为5Mb, 一本牛津词典的内容, 我觉得将reply放在这里应该不会有太大问题。 即便不存放reply 内容, 存放一个id数组也会好很多。
  //-- 客官们怎么看?  
  last_reply: { type: ObjectId },
  last_reply_at: { type: Date, default: Date.now },
  content_is_html: { type: Boolean }
});

var ReplySchema = new Schema({
  content: { type: String },
  topic_id: { type: ObjectId, index: true },
  author_id: { type: ObjectId },
  reply_id : { type: ObjectId },
  create_at: { type: Date, default: Date.now },
  update_at: { type: Date, default: Date.now },
  content_is_html: { type: Boolean }
});

//--话题集合
var TopicCollectSchema = new Schema({
  user_id: { type: ObjectId },
  topic_id: { type: ObjectId },
  create_at: { type: Date, default: Date.now }
});

//--话题标签
var TopicTagSchema = new Schema({
  topic_id: { type: ObjectId },
  tag_id: { type: ObjectId },
  create_at: { type: Date, default: Date.now }
});     
  • 3.2.6 tag 标签系统
//tag <- collect
var TagSchema = new Schema({
  name: { type: String },
  order: { type: Number, default: 1 },
  description: { type: String },
  background: { type: String },
  topic_count: { type: Number, default: 0 },
  collect_count: { type: Number, default: 0 },
  create_at: { type: Date, default: Date.now }
});

var TagCollectSchema = new Schema({
  user_id: { type: ObjectId, index: true },
  tag_id: { type: ObjectId },
  create_at: { type: Date, default: Date.now }
});
  • 3.2.7 关系
var RelationSchema = new Schema({
  user_id: { type: ObjectId },
  follow_id: { type: ObjectId },
  create_at: { type: Date, default: Date.now }
});
  • 3.2.8 消息 消息 model 设计, 对于一个 blog 来说, 基本的只有回复消息, 这里加了关注和@消息。
/*
 * type:
 * reply: xx 回复了你的话题
 * reply2: xx 在话题中回复了你
 * follow: xx 关注了你
 * at: xx @了你
 */
var MessageSchema = new Schema({
  type: { type: String },
  master_id: { type: ObjectId, index: true },
  author_id: { type: ObjectId },
  topic_id: { type: ObjectId },
  reply_id: { type: ObjectId },
  has_read: { type: Boolean, default: false },
  create_at: { type: Date, default: Date.now }
});

###3.3 require middlewares

  • 3.3.1 express 的基础是 middleware,或者说 express 的基础是 connect,connect 的基础是 middleware。middleware 模式在 professional nodejs 中有一个专门的章节来讲解。何为 middleware 呢? middleware 模式 相当于一个加工流水线(大家叫 middleware stack),每一个 middleware 相当于一个加工步骤,当出现一个 http 请求的时候,http 请求会挨着每个 middleware 执行下去。 express 里处理一个请求的过程基本上就是请求通过 middleware stack 的过程: * -> middlewares -> 路由 -> controllers -> errorhandlering。

  • 3.3.2 middleware 怎样做到的, 异步的方法呢? middleware 使用 promise 的方式来处理异步,所有每个 middleware 都有三个参数 req, res, next, 对于异步的情况, 必须要调用 next() 方法。不然后续的 middleware 就无法执行。 ps: debug 的时候没调用 next() 还不会报错,一定注意

  • 3.3.3 auth.js

auth.js exports 出来的函数全部都是中间件,从变量名就完全清楚的知道到底在做什么了


//-- 需要admin权限
exports.adminRequired = function (req, res, next) {}

//-- 需要有用户
exports.userRequired = function (req, res, next) {}

//-- 需要有用户并登录
exports.signinRequired = function (req, res, next) {
    if (!req.session.user) {
        res.render('notify/notify', {error: '未登入用户不能发布话题。'});
        return;
    }
    next();   
}

//-- 屏蔽用户 -_-
exports.blockUser = function (req, res, next) {}

这里其实就可以看到中间件的作用了,我们以前写 php 的时候每次都需要判断用户是否登录, 没登陆 redirect 到 index.php ,只不过这里的方式是通过中间件来处理。 明白这里什么意思,其他的中间件模块也就秒懂了。

###3.4 require(’./routes’)

  • 3.4.1 express 的世界里另外一个很重要的就是route, Node.js 启动的是服务, 监听了某一端口, 接受 http or https or socket 请求, 那 url 中像 /index.php?blabla 这一串的存在怎么处理呢, express 的 route 功能就可以帮我们解析。

  • 3.4.2 MVC 中如何将一个请求和 controller 联系起来呢, route 就是这样的纽带

//--get, post 请求
app.get('/signin', sign.showLogin);
app.post('/signin', sign.login);
//--使用中间件
app.get('/signup', configMiddleware.github, passport.authenticate('github'));
app.post('/:topic_id/reply', auth.userRequired, limit.postInterval, reply.add);
  • 3.4.3 route 是了解一个应用最佳的地方,一个请求如何处理, 到相应的 controller 去看就知道了。 相比起在PHP环境下配置更加灵活。当然你说你通过nginx来配置也很灵活,好吧,我们说的不是一回事。

3.5 initialization

  • 3.5.1 experess initialize: app.js 中其他大多部分就是express的初始化了, 初始化流程如下:
  • 1.配置上传 upload_dir
  • 2.模板引擎设置
  • 3.express 通用中间件设置
  • 4.pasport 中间件
  • 5.自定义中间件
    • 1.auth_user
    • 2.block_user
    • 3.staticfile: upload
    • 4.staticfile: user_data
  • 6.csrf
  • 7.errorhandler
  • 8.set view cache

[@Note](/user/Note):配置的顺序很重要, 中间件的执行顺序是按照定义顺序来执行的, 如果一个中间件依赖另外的中间件, 而自己先执行了, 这种情况就会错误。 常见的问题就是session配置, 一定要记得配置 session 中间件的时候, 要先配置 cookieParser。

  • 3.5.2 session 设置

这个步骤在 initialize 里边已经有了, 不过再单独讲一下, nodeclub 使用的是 connect-mongo 来作为 session 的存储

  //--cookieParser一定要在前面, 因为session的设置依赖cookie
  app.use(express.cookieParser());
  app.use(express.session({
    secret: config.session_secret,
    store: new MongoStore({
      db: config.db_name,
    }),
  }));
  • 3.5.3 view helpers

使用过 ejs 的肯定知道, ejs 里边 view helper 设置很简单, 就像赋值变量一样。 当对于一些通用的 helper 可以这样设置:

    app.helpers({
      config: config,
      Loader: Loader,
      assets: assets
    });
    app.dynamicHelpers(require('./common/render_helpers'));
  • 3.5.4 github pasport initialize
    // github oauth
    passport.serializeUser(function (user, done) {
      done(null, user);
    });
    passport.deserializeUser(function (user, done) {
      done(null, user);
    });
    passport.use(new GitHubStrategy(config.GITHUB_OAUTH, githubStrategyMiddleware));
  • 3.5.5 start app

##4. 用户注册

  • 4.1 user 是每个应用都会处理的基本, 注册登录登出, 看看 nodeclub 做了哪些事情:
  • 4.2 路由:
//--设置能否直接注册, 不能的话通过github注册
if (config.allow_sign_up) {
  app.get('/signup', sign.showSignup);
  app.post('/signup', sign.signup);
} else {
  app.get('/signup', configMiddleware.github, passport.authenticate('github'));
}
app.post('/signout', sign.signout);
app.get('/signin', sign.showLogin);
app.post('/signin', sign.login);
  • 4.3 controller & model:sign.signup
sanitize = validator.sanitize;
check = validator.check;
exports.signup = function (req, res, next) {
  //--xss 消毒
  var name = sanitize(req.body.name).trim();
  name = sanitize(name).xss();
  ...
  //--validations
  try {
    check(name, '用户名只能使用0-9,a-z,A-Z。').isAlphanumeric();
  } catch (e) {
    res.render('sign/signup', {error: e.message, name: name, email: email});
    return;
  }
  ...
  //--用用户名登录或者email登录
  query = {'$or': [{'loginname': loginname}, {'email': email}]}
  User.getUserByQuery(query, {}, function(){
    ...
    pass = md5(pass);
    ...
    User.newAndSave(name, loginname, pass, email, avatar_url, false, function (err) {
      ...
      // 发送激活邮件
      mail.sendActiveMail(email, md5(email + config.session_secret), name);
      res.render('sign/signup', {
        success: '欢迎加入 ' + config.name + '!我们已给您的注册邮箱发送了一封邮件,请点击里面的链接来激活您的帐号。'
      });
    })
  })
}   

##5. mongoose 的使用

  • 5.1 使用User.newAndSave,
  • 5.2 异步 callback pyramid

一个应用通常会遇到这样的情景, 一个页面需要的数据包括, 文章列表, 评论列表,用户数据,广告数据, other stuff… 问题是每个都是异步的, 怎么办。 user 数据获取过后的 callback 调用文章列表获取, 文章列表获取的 callback 调用评论列表的获取… 这样就太蛋疼了。 nodeclub 使用了 eventproxy 模块优雅的解决这样的问题:

  render = function(){}
  var proxy = EventProxy.create('tags', 'topics', 'hot_topics', 'stars', 'tops', 'no_reply_topics', 'pages', render);
  proxy.fail(next);
  Tag.getAllTags(proxy.done('tags'));
  Topic.getTopicsByQuery(query, options, proxy.done('topics'));
  User.getUsersByQuery({ is_star: true }, { limit: 5 }, proxy.done('stars'));

看完代码不言而喻。。。 当然异步处理的方法有很多:

  • 1.基于事件的:eventProxy
  • 2.基于promise的:Async.js Q.js, when.js
  • 3.基于编译的:continuation, wind
  • 4.基于语言语法的:yield, livescript

文章最后会讲一下我我的异步选择方案

##6. 消息

  • 6.1 原先以为有动态的消息推送, 有队列处理, 错了, 木有
  • 6.2 在 Sublime text 里边全局搜索 sendReply2Message 会发现是在 controller/reply.js 里边调用的, 也就是说,消息是直接触发的。
  • 6.3 好吧, 这部分大概大家都能秒懂。。

##7. 开发

###7.1 测试

  • 7.1.1 一个项目必定离不开测试, nodeclub基于mocha BDD测试框架, 一切的前提假设至少能看懂jasmine或者mocha或者任何一个BDD风格的测试代码。
  • 7.1.2 打开即看到app.js
var app = require('../app');
describe('app.js', function () {
  //--before, 执行it的前面会执行
  before(function (done) {
    //--done, 异步方法
    app.listen(3001, done);
  });
  after(function () {
    app.close();
  });
  it('should / status 200', function (done) {
    //--使用 app.request()就可以模拟请求了? 这个api哪里来的, 求解释?
    app.request().get('/').end(function (res) {
      res.should.status(200);
      done();
    });
  });
});
//--按理说应该是可以正常运行了但是我一直出现这个错误:
//--connect ADDRNOTAVAIL 知道的求解释
//--我尝试用supertest直接测试, 但是也是一直timeout, mocha
//--里边加大timeout时间, 结果就是一直没反应。 

//--分析原因, express版本问题, nodeclub中express的版本还是2.x, 所以才会有
//--app.request(), app.close()这些api
//--第二个原因, 到supertest官网, 发现人家都已经转战到superagent项目了, 于是我写了下面这个测试脚本, 可以通过了
var express = require('express');
var should = require('should');
var path = require('path');
var superagent = require('superagent');
var app = express()
app.get('/user', function(req, res, next) {
    res.send(200, {
        name: 'tobi'
    })
})
describe('myapp.js', function() {
    this.timeout(5000)
    before(function(done) {
        app.listen(21, done);
    })
    after(function() {
        // app.close()
    })
    it('should /status 200', function(done) {
        agent = superagent.agent()
        agent.get('http://localhost:21/user').end(function(err, res) {
          console.log(err, res)
          res.should.have.status(200);
          res.text.should.include('tobi');
          return done();
        });
    })
})

###7.2 运行

  • nodejs是单线程应用, 如果我们用node命令来运行我们的应用, 当出现一个小错误, 它就挂了。 然后没有然后了。 避免这种问题的方法有如下工具:
  • 1.forever
  • 2.nodemon
  • 3.supervisor nodeclub 使用 forever 来运行项目, 使用这类工具的好处就是, 当有代码改动过后, 会自动的重启应用。 不必每次自己去运行 node *.js

##8. 说说自己的经验

待续… ###8.1 消息订阅设计 ###8.2 express + socket ###8.3 异步 ###8.4 Action

85 回复

文章不错… 就是 Markdown 格式改了我好久…

good! 已置顶。坐等更新。

nodeclub 这么烂的代码都有人愿意来看,着实让人感动不已。

  1. 关于 mongoose schema 那里楼主的疑问,解答是:nodeclub 几乎没有利用 mongodb 的特性。

  2. nodeclub 的测试很不全,不过里面的测试大致上是最佳实践的样子。

火钳留名~支持下~

前排留名哈~

thx, 居然置顶了, 万分感动啊。 看你们代码说明是真爱啊, 哈哈!

恩, 测试部分很值得参考借鉴。

多谢支持 : -)

收藏了

感谢,对自己很有帮助 :-)

收藏,慢慢看。

nodeclub 代码说实话一般啊,坑也很多

赞! 源码看过几次了 但一直没有像你一样写下来和总结心得,值得学习!

求几个好的项目源码

nodeclub也是我看的第一个node的项目,不过当时就没作者(学家?)看的这么系统了,学习了!

额,最近沉迷nodejs,求教

本来我也想写一个学习文档的。 Node Club还是比较好的学习资料,认真的看代码,并且在基础上做一定的二次开发会比写书籍上面的小代码和写个blog锻炼人。 我前一个月用Node Club搭建了一个小区的社交平台 highhome 个人感觉是看朴灵的《深入浅出Node.js》 然后用Node Club练手会是比较好的node学习方式。 几点感受: 1.node club 好歹也算的上是完整的项目,比写个blog和聊天室要锻炼的人。可以看到node的文件目录建立,和MVC设计,这边可以结合《深入浅出Node.js》中的第8章 构建Web应用。总之node club可以被当做node学习从0到1的跨越。 2.express 2.x的版本比较老,比较蛋痛。社区可以考虑更新一下。 3.mongooseeventProxy是好东西。可以让网站的结构和mvc设计上一个档次。 4.Node Club中原来的tag标签功能和图片上传功能是被封死的,读者要在源代码上做一定的修改。 5.Node Club涉及到的异步操作不多,这部分内容大部分都被eventProxy代劳,只有在图片上传文件移动中有涉及。 6.实际发布中要用nginx方向链接,这边要去掉端口,在config中设置debug: false,邮件发送要在原来的基础上加上 secureConnection: true。 7.在cnode中搜索Node Club可以解决你在开发中遇到的问题。 8.测试这一块的内容看不懂,明天在对照书和楼主的笔记去好好看看。

很赞, 最近也是因为在做一个node项目, 太没有经验, 所有才想看一下一些现有的代码。 github上node-express-mogoose-demo那个项目其实也很赞, 在cnode上还看到了doubanj 以及 jsgen这两个项目也有很多学习借鉴的地方。

打开你的网站 , 设计很赞啊。 哈哈,除了动态栏消息全是div, 截取字符串没有去掉标签。

@huanghuiquan 小泉也来混了,哈哈!多看些项目 / packages的source code,自己也写写。。

希望可以给他贡献点力量啊

忘持续更新

看了评论,结合最近经验,感觉学习项目确实是一种很好的方式,我要坚持下来!加油,希望LZ持续更新!

翻了好多页终于翻出来了……前阵子置顶的时候没收藏真是重大失误

坐等更新啊

这里有个缺点,就是好贴沉了就看不见了…感觉前端乱炖蛮好的

@6174 新手建议顺道看看 bearcat,随心编写简单,可维护的node.js

部署运行好了,发现无法向 精华 模块写文章,求救。。

@enson16855 精华不是管理员来对普通文章加精的吗?

写的很好,不过不是很详细呀,坐等楼主完善

lz 请继续更新啊~~

其实我很想吐槽为什么不format一下

拋坟。。。。。。。努力拋坟。。。。。。。。

为什么现在的源码没有看到tag系统相关内容?

坐等更新啊

赞~刨~等更~

赞 正在学习nodejs

Good article. It makes me learn a lot about the project. Thanks.

支持,我现在回复的这个编辑器是什么 我觉得不错 想用一下

@alsotang 老大介么谦虚,期待您也能结合nodeclub的代码来个全栈开发的教程呗,跪了~~~!

先收藏了先

@zodiacg 现在怎么收藏呢?

源码在哪可以下载?

model 的 schema 设计 怎么看不到呢。数据库是怎么建立的? 不太了解

mark 初学者,学到东西了。谢谢~

<del>不是一个很好的示范</del> …suanle 我也重构不了

对于刚入门的我,看了好久。确实学到许多。来的太迟

我想问一下大神们,nodeclub中topic收藏是怎么做的,包括cnodejs论坛我也不会收藏,求不吝赐教

mark了

你猜我来自哪?

@alsotang 代码结构组织的很好啊,还有测试 我想问下你们是在项目着手前就考虑了测试部分还是?

@ncuzp 如果是现在写的话,项目肯定边写边测试。这个项目是慢慢演进过来的,重构了好几次了。

mark,正好想找个node项目研究下

@lawrence-peng 和cnodejs.org 的一样,Tag 的代码似乎也remove了,models 里面也体现不到

node.js 零基础 看了朴灵的教程,感觉不适合初学者 很不容易看到这里有实例教程,可惜作者不更了啊。。。。

@runkingzhang 实际发布中要用nginx方向链接,这边要去掉端口,这句话怎么理解啊?

小白求问,在config文件里面 session_secret 这个参数要修改成什么啊?我这边可以跑起来了,可是注册的时候不能发送验证邮箱?求指点一下

@alsotang 求指点一下,刚开始学node,想从这个代码看起

楼主还会更新么

楼主,经典啊

学习了

来自酷炫的 CNodeMD

nodeclub的代码我也翻看了很多次哈,还是很好懂的。 只是有些老了,什么时候重构哈。

好文笔

来自酷炫的 CNodeMD

项目是如何实现支持less文件的?

nodeclub 是如何让html直接可以处理less 文件的?

回到顶部