dx-server - modern, unopinionated, and satisfactory server
Install
yarn add dx-server jchain
Usage
Check below sample with comment for more details.
Sample additionally requires: yarn install express morgan
import {Server} from 'http'
import {promisify} from 'util'
import chain from 'jchain'
import {
makeContext, requestContext, responseContext,
expressContext, setHtml, setJson,
bufferBodyContext,
jsonBodyContext,
queryContext,
rawBodyContext,
textBodyContext,
urlencodedBodyContext,
router,
expressApp, expressRouter, chainExpressMiddlewares,
} from 'dx-server'
import express from 'express'
import morgan from 'morgan'
class ServerError extends Error {
name = 'ServerError'
constructor(message, status = 400, code = 'unknown') {
super(message)
this.status = status
this.code = code
}
}
const authContext = makeContext(() => {
const req = requestContext.value
if (req.headers.authorization) {
return {id: 1, name: 'joe'}
}
})
const requireAuth = () => {
if (!authContext.value) throw new ServerError('unauthorized', 401, 'unauthorized')
}
const serverChain = chain(
expressContext.chain({jsonBeautify: true}),
bufferBodyContext.chain(),
jsonBodyContext.chain(),
urlencodedBodyContext.chain(),
textBodyContext.chain(),
rawBodyContext.chain(),
queryContext.chain(),
next => {
responseContext.value.setHeader('cache-control', 'no-cache')
next()
},
async next => {
try {
await next()
} catch (e) {
if (e instanceof ServerError) setHtml(`${e.message} (code: ${e.code})`, {status: e.status})
else {
console.error(e)
setHtml('internal server error (code: internal)', {status: 500})
}
}
},
await expressApp(app => {
app.set('trust proxy', true)
if (process.env.NODE_ENV !== 'production') app.set('json spaces', 2)
}),
chainExpressMiddlewares(
morgan('common'),
),
await expressRouter(router => {
router.use('/public', express.static('public'))
}),
authContext.chain(),
router.post({
async '/api'({next}) {
try {
await next()
} catch (e) {
if (e instanceof ServerError) setJson({
error: e.message,
code: e.code,
}, {status: e.status})
else {
console.error(e)
setJson({
message: 'internal server error',
code: 'internal'
}, {status: 500})
}
}
}
}, {end: false}),
router.post({
'/api/sample-public-api'() {
setJson({name: 'joe'})
},
'/api/me'() {
requireAuth()
setJson({name: authContext.value.name})
},
}),
router.get({
'/'() {
setHtml('ok')
},
'/health'() {
setHtml('ok')
}
}),
router.post({
'/api'() {
throw new ServerError('not found', 404, 'not_found')
}
}, {end: false}),
() => {
throw new ServerError('not found', 404, 'not_found')
},
)
const tcpServer = new Server()
.on('request', async (req, res) => {
try {
await chain(
requestContext.chain(req),
responseContext.chain(res),
serverChain,
)()
} catch (e) {
console.error(e)
}
})
const port = +(process.env.PORT ?? 3000)
await promisify(tcpServer.listen.bind(tcpServer))(port)
console.log(`server is listening at ${port}`)
TODO
Until these middlewares are available as native dx-server middlewares, express middlewares can be used with expressApp()