封面. jpg
基于 Node.JS 的服务端开发已经有 express,koa2 等成熟框架. 熟练使用这些框架并不难, 但背后的原理是怎样的, 很多同学并没有做到知其所以然.
本 Node.JS 系列教程先抛开框架, 原生搭建 Node.JS 后服务, 在掌握原理后, 再去学习 express,koa2 框架.
通过开发一个 ToDoList 小项目, 掌握常用的 Node.JS 开发. 大致分为以下几个阶段:
[第一阶段] 不借助框架开发 Node.JS 后端服务, 包括数据的接收, 处理, 返回, 路由, MySQL,Redis 对接, 登录验证, 简单的安全防范等 (分较多期进行讲解).
[第二阶段] 使用 express 重构项目
[第三阶段] 使用 koa2 重构项目
第一阶段将花费大量篇幅讲解, 当我们深入了解原理后再去学习 express,koa2 就会理解得更加透彻.
重要的事情说三遍: 懂原理! 懂原理! 懂原理!
1 初始化项目
1.1 创建项目目录
找个喜欢的地方, 新建并初始化项目, 执行以下命令:
- mkdir node-server
- cd node-server
- NPM init -y
1.2 编写服务脚本
在项目根目录下创建 bin/www.JS.
- + |- /bin
- + |- www.JS
- |- package.JSON
启动 web 服务需要使用 Node.JS 的 http 模块, 打开 bin/www.JS 编写代码:
- const http = require('http')
- // 设置服务器端口
- const PORT = 8000
- // 创建服务器
- const server = http.createServer((req, res) => {
- // 返回的内容
- res.end('hello nodejs')
- });
- // 设置服务器端口
- server.listen(PORT)
- console.log('node-server started at port http://localhost:' + PORT)
配置入口文件, 修改 package.JSON
- "name": "node-server",
- "version": "1.0.0",
- "description": "",
- M "main": "./bin/www.js",
- "scripts": {
- "test": "echo \"Error: no test specified\"&& exit 1",
- + "dev": "node ./bin/www.js"
- },
[关于 main 字段]
官方说明的原文是这样的:
The main field is a module ID that is the primary entry point to your program. That is, if your package is named foo, and a user installs it, and then does require("foo"), then your main module's exports object will be returned.
This should be a module ID relative to the root of your package folder.
For most modules, it makes the most sense to have a main script and often not much else.
main 字段是一个模块 ID, 是指向你程序的主入口. 也就是说, 如果你的 package 名叫 foo, 那么一个用户安装了你这个 package, 并且执行 require("foo"), 你的 main 模块的 exports 对象将会被返回.
这个模块 ID 应该相对于你的 package 根目录.
对于大多数的模块来说, 设置 main 还是非常有意义的, 除此之外也没有其他鸟用了.
[关于 script 字段]
官方说明的原文是这样的:
The "scripts" property is a dictionary containing script commands that are run at various times in the lifecycle of your package. The key is the lifecycle event, and the value is the command to run at that point.
scripts 是一个包含了脚本命令的 dictionary, 可以在 package 不同的生命周期中被执行. key 是生命周期事件, value 是要运行的命令.
- "scripts": {
- "test": "echo \"Error: no test specified\"&& exit 1",
- M "dev": "cross-env NODE_ENV=dev nodemon ./bin/www.js"
- },
- const http = require('http')
- + const querystring = require('querystring')
- + // 设置服务器端口
- const PORT = 8000
- // 创建服务器
- const server = http.createServer((req, res) => {
- + // 获取请求的 url
- + const url = req.url
- + // 获取请求的 method
- + const method = req.method
- + // 解析 URL, 把 url 中? 后面的参数转换为对象
- + const query = querystring.parse(url.split('?')[1])
- + // 设置返回数据的 Content-type 为 JSON
- + res.setHeader('Content-type', 'application/json')
- + if (method === 'GET') {
- + // 返回的数据
- + let resData = {
- + error: 0,
- + message: 'GET 返回成功',
- + data: {
- + query: query
- + }
- + }
- + // 将对象转换为 JSON 字符串
- + res.end(JSON.stringify(resData));
- + return
- + }
- + // 如果没有匹配, 则返回 404 页面
- + res.writeHead(200, {'content-type': 'text/plain'});
- + res.write('404 Not Found\n')
- + res.end()
- - // 返回的内容 <-- 删除
- });
- // 设置服务器端口
- server.listen(PORT)
- console.log('node-server started at port http://localhost:' + PORT)
- ...(略)
- const server = http.createServer((req, res) => {
- const url = req.url
- const method = req.method
- const query = querystring.parse(url.split('?')[1])
- + const contentType = req.headers['content-type']
- // 设置返回数据的 Content-type 为 JSON
- res.setHeader('Content-type', 'application/json')
- if (method === 'GET') {...}
- + if (method === 'POST') {
- + if (contentType === 'application/json') {
- + let postData = '';
- + req.on('data', chunk => {
- + // chunk 是原始二进制数据, 需要转化成字符串
- + postData += chunk.toString()
- + })
- + req.on('end', () => {
- + res.end(postData)
- + })
- + return
- + }
- + }
- ...(略)
- var formData = new FormData();
- formData.append('username', 'admin')
- formData.append('password', 123456)
- $.Ajax({
- type: 'POST',
- url: 'api/',
- data: formData,
- dataType: 'json',
- processData: false,
- contentType: false,
- success: function(obj) {
- console.log(obj)
- }
- })
- ------WebKitFormBoundaryQrEkLmfuhHhIu5vy
- Content-Disposition: form-data; name="username"
- admin
- ------WebKitFormBoundaryQrEkLmfuhHhIu5vy
- Content-Disposition: form-data; name="password"
- 123456
- ------WebKitFormBoundaryQrEkLmfuhHhIu5vy--
- const http = require('http')
- const querystring = require('querystring')
- + const multiparty = require('multiparty')
- const PORT = 8000
- const server = http.createServer((req, res) => {
- ...(略)
- if (method === 'GET') {...}
- if (method === 'POST') {
- if (contentType === 'application/json') { ... }
- + else if (contentType.indexOf('multipart/form-data') !== -1) {
- + let form = new multiparty.Form()
- + form.parse(req, function(err, fields, files) {
- + res.end(JSON.stringify({fields: fields, files: files}));
- + });
- + return
- }
- }
- ...(略)
来源: http://www.jianshu.com/p/4fed8bd784d1