├── .gitignore ├── README.md ├── app.js ├── bin ├── cli.js └── www ├── doc.md ├── package.json ├── routes └── index.js ├── test ├── posts.get ├── posts.json.get └── posts.json.get?a=1&b=2 └── views ├── error.jade └── layout.jade /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # json-file-server 2 | [![NPM Version](http://img.shields.io/npm/v/json-file-server.svg?style=flat)](https://www.npmjs.org/package/json-file-server) 3 | 4 | 对于前后端完全分离的项目来说,前端只会关心接口,通讯方式一般采用json。所以,理想的状态下就是前端不用直接等待后端完成接口再写逻辑代码,而是直接自己fake一些数据。 5 | 6 | 参考过[json-server](https://github.com/typicode/json-server),能满足部分需求,然而在实际的上环境比较复杂,以下这2个需求是我迫切需要: 7 | 8 | * 请求一些非RESTFUL API (GET /posts/1.json) 9 | * URL有后缀 [add suffix in url #161]() 10 | * fake的数据较大时,比较查看`db.json` 难以看出其数据结构 11 | 12 | 个人感觉最理想的情况下是,自己根据URL,构成文件夹,文件目录,编写所需的返回数据 13 | 14 | ### 编写mock file(草案) 15 | 16 | URL: `/path/to/Resource[.suffix|''][.Http_Method][?QueryString|'']` 17 | 18 | 对应文件目录结构: 19 | ``` 20 | --... 21 | --path 22 | --blabla 23 | --to 24 | --Resource[.suffix|''][.Http_Method|''][?QueryString|''] 25 | ``` 26 | 27 | * Http_Method: Http方法 GET POST PUT DELETE PATCH ..... 28 | * suffix 后缀 29 | * LangPrefix: 语言占位符 e.g: `php`, `do`, `aspx` 30 | * ReturnType: 返回类型: e.g: `json` & `xml` , 31 | * QueryString: QueryString,参数顺序问题值得商讨 32 | 33 | 34 | #### Example 35 | 36 | Mock File Name|对应请求方法 37 | -----|----- 38 | `/posts.get` | `GET /posts` 39 | `/posts.post`| `POST /posts` 40 | `/posts.json.post` | `POST /posts.json` 41 | `/post.php.get`| `GET /post.php` 42 | `/posts/1.get?a=1&b=2` | `GET /posts/1?a=1&b=2` 43 | `/posts/1.json.get?a=1` | `GET /posts/1.json?a=1` 44 | 45 | 46 | ### Getting Start 47 | 48 | ```shell 49 | npm install -g json-file-server 50 | cd path/to/json-file-server # optionals 51 | jfs run -p 3000 . 52 | jfs -help 53 | ``` 54 | 55 | # License 56 | 57 | MIT @[Jayin Ton](http://www.jayinton.com) 58 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var favicon = require('serve-favicon'); 4 | var logger = require('morgan'); 5 | var cookieParser = require('cookie-parser'); 6 | var bodyParser = require('body-parser'); 7 | var cors = require('cors'); 8 | 9 | var routes = require('./routes/index'); 10 | 11 | var app = express(); 12 | 13 | // view engine setup 14 | app.set('view engine', 'jade'); 15 | 16 | // uncomment after placing your favicon in /public 17 | //app.use(favicon(__dirname + '/public/favicon.ico')); 18 | 19 | app.use(cors()); 20 | app.use(logger('dev')); 21 | app.use(bodyParser.json()); 22 | app.use(bodyParser.urlencoded({ extended: false })); 23 | app.use(cookieParser()); 24 | // app.use(express.static(path.join(__dirname, 'public'))); 25 | 26 | app.use('/', routes); 27 | 28 | // catch 404 and forward to error handler 29 | app.use(function(req, res, next) { 30 | var err = new Error('Not Found'); 31 | err.status = 404; 32 | next(err); 33 | }); 34 | 35 | // error handlers 36 | 37 | // development error handler 38 | // will print stacktrace 39 | if (app.get('env') === 'development') { 40 | app.use(function(err, req, res, next) { 41 | res.status(err.status || 500); 42 | res.render(path.join(__dirname,'views', 'error'), { 43 | message: err.message, 44 | error: err 45 | }); 46 | }); 47 | } 48 | 49 | // production error handler 50 | // no stacktraces leaked to user 51 | app.use(function(err, req, res, next) { 52 | res.status(err.status || 500); 53 | res.render(path.join(__dirname,'views', 'error'), { 54 | message: err.message, 55 | error: {} 56 | }); 57 | }); 58 | 59 | module.exports = app; 60 | -------------------------------------------------------------------------------- /bin/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var program = require('commander') 4 | var app = require('../app') 5 | var fs = require('fs') 6 | var package = require('../package.json') 7 | 8 | program 9 | .version(package.version) 10 | 11 | program 12 | .command('run [folder]') 13 | .description('run json file server') 14 | .option('-p, --port [port]', 'the port to listen on') 15 | .action(function(folder, options) { 16 | var base_path = folder || '.' 17 | var port = options.port || 3000 18 | app.set('base_path', base_path) 19 | app.listen(port, function() { 20 | console.log('Now working on http://127.0.0.1:%d ,serving `%s`', port, base_path) 21 | }) 22 | }); 23 | 24 | program 25 | .parse(process.argv); 26 | -------------------------------------------------------------------------------- /bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('jfs:server'); 9 | var http = require('http'); 10 | 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.PORT || '3000'); 16 | app.set('port', port); 17 | 18 | /** 19 | * Create HTTP server. 20 | */ 21 | 22 | var server = http.createServer(app); 23 | 24 | /** 25 | * Listen on provided port, on all network interfaces. 26 | */ 27 | 28 | server.listen(port); 29 | server.on('error', onError); 30 | server.on('listening', onListening); 31 | 32 | /** 33 | * Normalize a port into a number, string, or false. 34 | */ 35 | 36 | function normalizePort(val) { 37 | var port = parseInt(val, 10); 38 | 39 | if (isNaN(port)) { 40 | // named pipe 41 | return val; 42 | } 43 | 44 | if (port >= 0) { 45 | // port number 46 | return port; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | /** 53 | * Event listener for HTTP server "error" event. 54 | */ 55 | 56 | function onError(error) { 57 | if (error.syscall !== 'listen') { 58 | throw error; 59 | } 60 | 61 | var bind = typeof port === 'string' 62 | ? 'Pipe ' + port 63 | : 'Port ' + port; 64 | 65 | // handle specific listen errors with friendly messages 66 | switch (error.code) { 67 | case 'EACCES': 68 | console.error(bind + ' requires elevated privileges'); 69 | process.exit(1); 70 | break; 71 | case 'EADDRINUSE': 72 | console.error(bind + ' is already in use'); 73 | process.exit(1); 74 | break; 75 | default: 76 | throw error; 77 | } 78 | } 79 | 80 | /** 81 | * Event listener for HTTP server "listening" event. 82 | */ 83 | 84 | function onListening() { 85 | var addr = server.address(); 86 | var bind = typeof addr === 'string' 87 | ? 'pipe ' + addr 88 | : 'port ' + addr.port; 89 | debug('Listening on ' + bind); 90 | } 91 | -------------------------------------------------------------------------------- /doc.md: -------------------------------------------------------------------------------- 1 | # CLI 2 | 3 | ``` 4 | jfs [path] 5 | ``` 6 | 7 | `--port`:端口 8 | `--cors`:支持跨域 9 | 10 | 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "json-file-server", 3 | "version": "1.2.3", 4 | "repository": "https://github.com/Jayin/json-file-server", 5 | "license": "MIT", 6 | "scripts": { 7 | "start": "node ./bin/www" 8 | }, 9 | "bin": { 10 | "jfs": "bin/cli.js" 11 | }, 12 | "dependencies": { 13 | "body-parser": "~1.12.0", 14 | "commander": "^2.8.1", 15 | "cookie-parser": "~1.3.4", 16 | "cors": "^2.7.1", 17 | "debug": "~2.1.1", 18 | "express": "~4.12.2", 19 | "jade": "~1.9.2", 20 | "lodash": "^3.10.0", 21 | "morgan": "~1.5.1", 22 | "serve-favicon": "~2.2.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var express = require('express') 3 | var router = express.Router() 4 | var path = require('path') 5 | 6 | 7 | router.all('*', function(req, res, next) { 8 | var base_path = path.resolve(process.cwd(), req.app.get('base_path')) 9 | var f = req.url + '.' + req.method.toLowerCase() 10 | // without querystring 11 | if(fs.existsSync(path.join(base_path, f))){ 12 | res.sendFile(path.join(base_path, f)) 13 | return 14 | } 15 | if(req.url.indexOf('?') == -1){ 16 | res.status(404).send('NOT FILE') 17 | return 18 | } 19 | // with query string 20 | var prefix = req.url.split('?')[0] 21 | var full_prefix = prefix + '.' + req.method.toLowerCase() 22 | var _dir = req.url.substring(0, prefix.lastIndexOf('/')) 23 | 24 | if(fs.existsSync(path.join(base_path, _dir))){ 25 | var posible_files = fs.readdirSync(path.join(base_path, _dir)) 26 | 27 | for(var i=0; i