あいつの日誌β

働きながら旅しています。

Single Page Application の Mock Server を作る 2016

対象読者

以下のワードに興味が無い方はたぶん読まなくても良い記事です。

  • json-serverjwt
  • browsersynchttp-proxy-middleware
  • npm-run-all --parallel

準備

% mkdir practice-mock-api && cd $_
% mkdir -p mock src/components www
% npm init -y

json-server と jwt

json-server は express がベースとなっているので少し手を加えて jwt が関わる処理を追加します。 db.json は jsonplaceholder のデータを流用します。

% npm install --save-dev json-server body-parser jsonwebtoken express-jwt
% curl http://jsonplaceholder.typicode.com/db -o ~/practice-mock-api/mockServer/db.json

create mockServer.js:

const jsonServer = require('json-server')
const server = jsonServer.create()
const router = jsonServer.router('./mock/db.json')
const jwt = require('jsonwebtoken')
const expressJwt = require('express-jwt')
const bodyParser = require('body-parser')

server.use(bodyParser.urlencoded({ extended: false }))
server.use(bodyParser.json())

const SERCRET = "shhhhhhared-secret"

function createToken() {
  return jwt.sign({
    exp: Math.floor(Date.now() / 1000) + (60 * 60), // 1 hour
    data: {
      username: "okamuuu",
      roles: ["operator", "admin"],
    }
  }, SERCRET)
}

const token = createToken()

console.log("=== create new token ===")
console.log(token)

jwt.verify(token, SERCRET, (err, decoded) => {
  console.log("=== decoded ===")
  console.log(decoded)
})

server.post('/auth/login', (req, res) => {
  res.writeHead(200, { 'Content-Type': 'application/json' })
  res.end(JSON.stringify({token: createToken()}))
})

server.post('/auth/refresh', expressJwt({secret: SERCRET}), (req, res) => {
  res.writeHead(200, { 'Content-Type': 'application/json' })
  res.end(JSON.stringify({token: createToken()}))
})

server.use('/api', expressJwt({secret: SERCRET}), router)

server.listen(3000, function () {
  console.log("=== Mock API Server is running ===")
  console.log("=== USAGE ===")
  console.log(`curl -H \"Authorization: Bearer ${token}\" http://localhost:3000/api/posts`)
})

起動するとこんな感じです。

% node mockServer.js
=== create new token ===
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0ODAzOTAxNTksImRhdGEiOnsidXNlcm5hbWUiOiJva2FtdXV1Iiwicm9sZXMiOlsib3BlcmF0b3IiLCJhZG1pbiJdfSwiaWF0IjoxNDgwMzg2NTU5fQ.s5f1tRvFCwIZeJant3LLyyqWcVFlny4oQRv6KJ3PCRM
=== decoded ===
{ exp: 1480390159,
  data: { username: 'okamuuu', roles: [ 'operator', 'admin' ] },
  iat: 1480386559 }
=== Mock API Server is running ===
=== USAGE ===
curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0ODAzOTAxNTksImRhdGEiOnsidXNlcm5hbWUiOiJva2FtdXV1Iiwicm9sZXMiOlsib3BlcmF0b3IiLCJhZG1pbiJdfSwiaWF0IjoxNDgwMzg2NTU5fQ.s5f1tRvFCwIZeJant3LLyyqWcVFlny4oQRv6KJ3PCRM" http://localhost:3000/api/posts

動作確認をします。無事 JWT で認証する必要がある WEB API の mock ができました。

% TOKEN=`curl -s -X "POST" http://localhost:3000/auth/login | jq .token -r`
% curl -s -H "Authorization: Bearer $TOKEN" http://localhost:3000/api/posts | jq length
100

browsersync と http-proxy-middleware

前回の記事で紹介したように BroserSync と webpack を使って Hot Module Replacement するためのサーバーを作成します。 前回の記事との違いは port の指定と起動する度にブラウザが open しないようにオプションを追加していて、http-proxy-middleware を使って /api/auth へのリクエストを mockServer へ渡すようにしています。

% npm install --save react react-dom
% npm install --save-dev babel-core babel-loader babel-preset-es2015 babel-preset-stage-0 babel-preset-react
% npm install --save-dev browser-sync webpack webpack-dev-middleware webpack-hot-middleware http-proxy-middleware

create webpack.config.js

const path = require('path')
const webpack = require('webpack')

module.exports = {
  debug: true,
  devtool: 'inline-source-map',
  entry: [
    'webpack/hot/dev-server',
    'webpack-hot-middleware/client',
    './src/index.js'
  ],
  output: {
    publicPath: "/",
    path: path.resolve('www'),
    filename: "bundle.js",
  },
  plugins: [
    new webpack.optimize.OccurenceOrderPlugin(),
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoErrorsPlugin()
  ],
  resolve: {
    extensions: ['', '.js'],
  },
  module: {
    loaders:[
      { test: /\.jsx?$/, exclude: /node_modules/, loaders: ['babel'] }
    ]
  }
}

create devServer.js

var webpack = require('webpack')
var proxy = require('http-proxy-middleware');
var browserSync = require('browser-sync')
var webpackDevMiddleware = require('webpack-dev-middleware')
var webpackHotMiddleware = require('webpack-hot-middleware')

var config = require('./webpack.config')
var bundler = webpack(config)

var apiProxy = proxy('/api', {
  target: 'http://localhost:3000',
  logLevel: 'debug'
});

var authProxy = proxy('/auth', {
  target: 'http://localhost:3000',
  logLevel: 'debug'
});

browserSync({
  port: 8080,
  open: false,
  server: {
    baseDir: config.output.path,
    middleware: [
      apiProxy,
      authProxy,
      webpackDevMiddleware(bundler, {
        publicPath: config.output.publicPath,
        stats: { colors: true }
      }),
      webpackHotMiddleware(bundler)
    ]
  },
  files: [
    'www/*.html'
  ]
})

create .babelrc

{
  "presets": ["es2015", "stage-0", "react"]
}

create www/index.html

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  </head>
  <body>
    <div id="root"></div>
    <script src="/bundle.js"></script>
  </body>
</html>

create src/index.js

import React from 'react'
import { render } from 'react-dom'
import App from './components/App'

render(
  <App />, 
  document.getElementById('root')
)

if (module.hot) {
  module.hot.accept('./components/App.js', function() {
    const NextApp = require('./components/App').default;
    render(<NextApp/>, document.getElementById('root'));
  }); 
}

create scr/components/App.js

import React from 'react'

export default (props) => (
  <h1>HMR test</h1>
)

起動

% node devServer.js

npm-run-all

最後に2つのフォアグランドプロセスをまとめて実行、停止できるようにします。

% npm install --save-dev npm-run-all

edit package.json

  "scripts": {
    "mock-server": "node mockServer.js",
    "dev-server": "node devServer.js",
    "serve": "npm-run-all --parallel mock-server dev-server"
  },  

rpm run serveCtrl-C を使って起動、停止ができます。

SEE ALSO

https://github.com/chimurai/http-proxy-middleware/blob/master/examples/browser-sync/index.js