Ciro Santilli OurBigBook.com  Sponsor 中国独裁统治 China Dictatorship 新疆改造中心、六四事件、法轮功、郝海东、709大抓捕、2015巴拿马文件 邓家贵、低端人口、西藏骚乱
nodejs/express/index.js
#!/usr/bin/env node

// https://cirosantilli.com/express-js

const assert = require('assert')
const http = require('http')

const express = require('express')

// Test it.
function test(port, path, method, status, resBodyExpect, reqBody) {
  const options = {
    hostname: 'localhost',
    port,
    path,
    method,
  }
  const req = http.request(options, res => {
    console.log([path, method, status, resBodyExpect]);
    assert.strictEqual(res.statusCode, status);
    res.on('data', d => {
      if (resBodyExpect !== undefined) {
        assert.strictEqual(d.toString(), resBodyExpect);
      }
    })
  })
  if (reqBody !== undefined) {
    req.write(reqBody)
  }
  req.end()
}

function check_helper(req, res) {
  if (req.params.param.length > 2) {
    res.status(404)
    res.send('ko')
  } else {
    return req.params.param + 'ok'
  }
}

const tests = [
  ['/hello', 'GET', 200, 'hello world'],
  ['/', 'POST', 404],
  ['/form', 'GET', 200],
  // TODO lazy
  // Need to set headers as well likely.
  // https://nodejs.dev/learn/make-an-http-post-request-using-nodejs
  //['/form', 'POST', 200, 'aa: 00 bb: 11', 'aa=00&bb=11'],
  ['/dontexist', 'GET', 404],

  ['/query?aa=000&bb=111', 'GET', 200, 'aa: 000 bb: 111'],
  // Repeated params becomes lists instead of strings. Type safety is for the weak.
  // https://stackoverflow.com/questions/24059773/correct-way-to-pass-multiple-values-for-same-parameter-name-in-get-request
  ['/query?aa=000&aa=111', 'GET', 200, 'aa: 000,111 bb: undefined'],

  // https://stackoverflow.com/questions/10020099/express-js-routing-optional-splat-param
  // https://stackoverflow.com/questions/16829803/express-js-route-parameter-with-slashes
  // https://stackoverflow.com/questions/34571784/how-to-use-parameters-containing-a-slash-character
  ['/splat/aaa', 'GET', 200, 'splat aaa'],
  ['/splat/aaa/bbb', 'GET', 200, 'splat aaa/bbb'],
  ['/splat/aaa/bbb/ccc', 'GET', 200, 'splat aaa/bbb/ccc'],

  ['/check-helper-1/aa', 'GET', 200, 'aaok'],
  ['/check-helper-2/bb', 'GET', 200, 'bbok'],
  ['/check-helper-1/ccc', 'GET', 404, 'ko'],
  ['/check-helper-2/ddd', 'GET', 404, 'ko'],

  // Shows stack traceon terminal.
  ['/error', 'GET', 500],

  // Shows stack traceon terminal.
  ['/error/custom-handler', 'GET', 500],

  // The default errot handler inspects the .status
  // property of the error. 4xx and 5xx are kept.
  // everything else is replaced with 500.
  //
  // With NODE_ENV=production however
  // it is coded  to omit stack traces,
  // and show just the error name.
  ['/error/code/404', 'GET', 404],
  ['/error/code/505', 'GET', 505],
  ['/error/code/606', 'GET', 500],
]

const app = express()
app.use(express.urlencoded({ extended: true }))
app.get('/', (req, res) => {
  res.send(`<!doctype html>
<html lang=en>
<head>
<meta charset=utf-8>
<title>Min sane</title>
</head>
<body>
<ul>
${tests.filter(t => t[1] === 'GET').map(t => `<li><a href="${t[0]}">${t[0]}</a></li>`).join('\n')}
</ul>
</body>
</html>
`)
})
app.get('/hello', (req, res) => {
  res.send('hello world')
})
app.get('/form', (req, res) => {
  res.send(`<!doctype html>
<html lang=en>
<head>
<meta charset=utf-8>
<title>Min sane</title>
</head>
<body>
<form action="/form" method="post">
  <input type="text" name="aa" placeholder="aa"><br>
  <input type="text" name="bb" placeholder="bb"><br>
  <input type="submit" value="Submit">
</form>
</body>
</html>
`)
})
app.post('/form', (req, res) => {
  // Requires the middleware:
  // app.use(express.urlencoded
  console.error(req.body);
  res.send(`aa: ${req.body.aa} bb: ${req.body.bb}`)
})
app.get('/check-helper-1/:param', (req, res) => {
  const ret = check_helper(req, res)
  if (ret) {
    res.send(ret)
  }
})
app.get('/check-helper-2/:param', (req, res) => {
  const ret = check_helper(req, res)
  if (ret) {
    res.send(ret)
  }
})
app.get('/query', (req, res) => {
  res.send(`aa: ${req.query.aa} bb: ${req.query.bb}`)
})
app.get('/splat/:splat(*)', (req, res) => {
  res.send('splat ' + req.params.splat)
})

// Error cases.
app.get('/error', async (req, res, next) => {
  try {
    asdfqwer
  } catch(error) {
    next(error);
  }
})
app.get('/error/code/:param', async (req, res, next) => {
  try {
    throw {
      status: parseInt(req.params.param, 10),
      stack: `fake stack ${req.params.param}`
    }
  } catch(error) {
    next(error);
  }
})
app.get('/error/custom-handler',
  async (req, res, next) => {
    try {
      asdfqwer
    } catch(error) {
      next(error);
    }
  },
  async (err, req, res, next) => {
    res.locals.msg = ['Custom handler']
    next(err)
  },
  async (err, req, res, next) => {
    res.locals.msg.push('Custom handler 2')
    res.status(500).send(res.locals.msg.join('\n'))
    // If we did next() here again, it falls down
    // to the default handler.
    //next(err)
  }
)

app.listen(3000, function () {
  const port = this.address().port
  console.log(`listening: http://localhost:${port}`)
  for (let atest of tests) {
    console.error(atest);
    test(port, ...atest)
  }
})