@idio/core
v1.4.0
Published
A Koa2-Based Web Server With Essential Middleware, Including Sessions, File Upload, Body Parser, CSRF Protection, Logging, Compression, Static Files And Router.
Downloads
213
Maintainers
Readme
@idio/core
@idio/core
is a Koa2-based web server with some pre-installed middleware which facilitates quick creation of a web server with the essential functionality, such as serving static files, compression, body parsing, etc. It also provides full JSDoc documentation of all options for completion in IDEs. Other components such as @idio/database
, @idio/route
and @idio/jsx
allow to build more complex websites (to come).
yarn add -E @idio/core
Table Of Contents
- Table Of Contents
- API
async core(middlewareConfig?: MiddlewareConfig, config?: Config): IdioCore
- Middleware Configuration
- Custom Middleware
- Router Set-up
- Copyright
API
The package is available by importing its default function:
import idioCore from '@idio/core'
async core(
middlewareConfig?: MiddlewareConfig,
config?: Config,
): IdioCore
The @idio/core
accepts 2 arguments which are the middleware configuration object and server configuration object. It is possible to start the server without any configuration, however it will do nothing, therefore it is important to add some middleware configuration.
MiddlewareConfig
: Middleware configuration for the idio
core
server.
| Name | Type | Description |
| ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| session | SessionOptions | session
options. |
| multer | MulterOptions | multer
options. |
| csrf | CSRFOptions | csrf
options. |
| bodyparser | BodyparserOptions | bodyparser
options. |
| compress | CompressOptions | compress
options. |
| checkauth | CheckauthOptions | checkauth
options. |
| logger | LoggerOptions | logger
options. |
| static | StaticOptions | static
options. |
| cors | CorsOptions | cors
options. |
| frontend | FrontendOptions | frontend
options. If the option is specified, the middleware always will be used, i.e., no need to pass use: true
. |
Config
: Server configuration object.
| Name | Type | Description | Default |
| ---- | --------------- | -------------------------------------- | --------- |
| port | number | The port on which to start the server. | 5000
|
| host | string | The host on which to listen. | 0.0.0.0
|
The return type contains the URL, Application and Router instances, and the map of configured middleware, which could then be passed to the router.
import('@goa/koa').Application
_goa.Application
: An instance of the Koa application.
import('@goa/koa').Middleware
_goa.Middleware
: An async middleware function.
import('koa-router').Router
koa-router.Router
: An instance of the Koa router.
import('http').Server
http.Server
: An instance of the Node's Server class.
IdioCore
: An object containing the url and references to the app, router and middleware.
| Name | Type | Description | Default |
| ---------- | ----------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | ----------------------- |
| url | string | The url on which the server is accessible. | http://localhost:5000
|
| app | _goa.Application | The Koa
application. | - |
| router | Router | The koa-router
instance. | - |
| server | http.Server | The http
server instance. | - |
| middleware | Object<string, _goa.Middleware> | The map of configured middleware functions which could then be set up to be used on a certain route. | - |
To start the server, the async method needs to be called and passed the middleware and server configuration objects. For example, the following code will start a server which serves static files with enabled compression.
import idioCore from '@idio/core'
const Server = async () => {
const { url } = await idioCore({
logger: {
use: true,
},
static: {
use: true,
root: 'example/static',
mount: '/static',
},
compress: {
use: true,
config: {
threshold: 1024,
},
},
}, {
port: 8080,
})
console.log('File available at: %s/static/test.txt', url)
}
File available at: http://localhost:8080/static/test.txt
Middleware Configuration
The middleware can be configured according to the MiddlewareConfig
. @idio/core
comes with some installed middleware as dependencies to speed up the process of creating a web server. Moreover, any custom middleware which is not part of the bundle can also be specified here (see Custom Middleware).
Each middleware accepts the following properties:
| Property | Description | Default |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| use
| Whether to use this middleware for every request. If not set to true
, the configured middleware function will be included in the middleware
property of the returned object, which can then be passed to a router configuration (not part of the @idio/core
). | false
|
| config
| Configuration object expected by the middleware constructor. | {}
|
| ...props
| Any additional specific properties (see individual middleware configuration). | |
SessionOptions
| Name | Type | Description | Default |
| --------- | ------------------------------------------------------------------------------------------------------- | -------------------------------------------- | ------- |
| keys* | string[] | A set of keys to be installed in app.keys
. | - |
| use | boolean | Use this middleware for every request. | false
|
| config | SessionConfig | koa-session
configuration. | - |
SessionConfig
: Configuration passed to koa-session
.
| Name | Type | Description | Default |
| --------- | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- |
| key | string | Cookie key. | koa:sess
|
| maxAge | (number | 'session') | maxAge in ms with default of 1 day. session
will result in a cookie that expires when session/browser is closed. Warning: If a session cookie is stolen, this cookie will never expire. | 86400000
|
| overwrite | boolean | Can overwrite or not. | true
|
| httpOnly | boolean | httpOnly or not. | true
|
| signed | boolean | Signed or not. | true
|
| rolling | boolean | Force a session identifier cookie to be set on every response. The expiration is reset to the original maxAge, resetting the expiration countdown. | false
|
| renew | boolean | Renew session when session is nearly expired, so we can always keep user logged in. | false
|
MulterOptions
| Name | Type | Description | Default |
| ------ | ------------------------------------------- | -------------------------------------- | ------- |
| use | boolean | Use this middleware for every request. | false
|
| config | MulterConfig | koa-multer
configuration. | - |
import('http').IncomingMessage
http.IncomingMessage
import('fs').Stats
fs.Stats
import('koa-multer').StorageEngine
koa-multer.StorageEngine
import('koa-multer').File
koa-multer.File
Limits
: An object specifying the size limits.
| Name | Type | Description | Default |
| ------------- | --------------- | ---------------------------------------------------------------------------- | ---------- |
| fieldNameSize | number | Max field name size in bytes. | 100
|
| fieldSize | number | Max field value size in bytes. | 1024
|
| fields | number | Max number of non-file fields. | Infinity
|
| fileSize | number | For multipart forms, the max file size in bytes. | Infinity
|
| files | number | For multipart forms, the max number of file fields. | Infinity
|
| parts | number | For multipart forms, the max number of parts (fields + files). | Infinity
|
| headerPairs | number | For multipart forms, the max number of header key=> value pairs to parse. | 2000
|
MulterConfig
| Name | Type | Description | Default |
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | ------- |
| dest | string | Where to store the files. | - |
| storage | StorageEngine | Where to store the files. | - |
| fileFilter | (req: IncomingMessage, file: File, callback: (error: (Error|null), acceptFile: boolean)) => void | Function to control which files are accepted. | - |
| limits | Limits | Limits of the uploaded data. | - |
| preservePath | boolean | Keep the full path of files instead of just the base name. | false
|
CSRFOptions
| Name | Type | Description | Default |
| ------ | --------------------------------------- | -------------------------------------- | ------- |
| use | boolean | Use this middleware for every request. | false
|
| config | CSRFConfig | koa-csrf
configuration. | - |
CSRFConfig
| Name | Type | Description | | ------------------------------ | ----------------- | ----------- | | invalidSessionSecretMessage | string | | | invalidSessionSecretStatusCode | number | | | invalidTokenMessage | string | | | invalidTokenStatusCode | number | | | excludedMethods | string[] | | | disableQuery | boolean | |
BodyparserOptions
| Name | Type | Description | Default |
| ------ | --------------------------------------------------- | -------------------------------------- | ------- |
| use | boolean | Use this middleware for every request. | false
|
| config | BodyparserConfig | koa-bodyparser
configuration. | - |
import('koa').Context
koa.Context
BodyparserConfig
| Name | Type | Description | Default |
| ----------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | ------------------ |
| enableTypes | string[] | Parser will only parse when request type hits enableTypes. | ['json', 'form']
|
| encode | string | Requested encoding. | utf-8
|
| formLimit | string | Limit of the urlencoded body. If the body ends up being larger than this limit a 413 error code is returned. | 56kb
|
| jsonLimit | string | Limit of the json body. | 1mb
|
| strict | boolean | When set to true, JSON parser will only accept arrays and objects. | true
|
| detectJSON | (ctx: Context) => boolean | Custom json request detect function. | null
|
| extendTypes | { json: string[], form: string[], text: string[] } | Support extend types. | - |
| onerror | (err: Error, ctx: Context) => void | Support custom error handle. | - |
CheckauthOptions
| Name | Type | Description | Default |
| ---- | ---------------- | -------------------------------------- | ------- |
| use | boolean | Use this middleware for every request. | false
|
LoggerOptions
| Name | Type | Description | Default |
| ------ | ------------------------------------------- | -------------------------------------- | ------- |
| use | boolean | Use this middleware for every request. | false
|
| config | LoggerConfig | koa-logger
configuration. | - |
LoggerConfig
| Name | Type | Description |
| ----------- | ---------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| transporter | (str: string, args: [string, string, string, string, string, string, string]) => void | Param str
is output string with ANSI Color, and you can get pure text with other modules like strip-ansi
. Param args
is a array by [format, method, url, status, time, length]
. |
CompressOptions
| Name | Type | Description | Default |
| ------ | ----------------------------------------------- | -------------------------------------- | ------- |
| use | boolean | Use this middleware for every request. | false
|
| config | CompressConfig | koa-compress
configuration. | - |
CompressConfig
| Name | Type | Description | Default |
| ----------- | ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------- | ------- |
| filter | (content_type: string) => boolean | An optional function that checks the response content type to decide whether to compress. By default, it uses compressible
. | - |
| threshold | number | Minimum response size in bytes to compress. | 1024
|
| flush | number | Default: zlib.constants.Z_NO_FLUSH
. | - |
| finishFlush | number | Default: zlib.constants.Z_FINISH
. | - |
| chunkSize | number | Default: 16*1024
. | - |
| windowBits | number | Support extend types. | - |
| level | number | Compression only. | - |
| memLevel | number | Compression only. | - |
| strategy | number | Compression only. | - |
| dictionary | * | Deflate/inflate only, empty dictionary by default. | - |
StaticOptions
| Name | Type | Description | Default |
| --------- | ------------------------------------------- | ------------------------------------------------- | ------- |
| root* | (string | string[]) | Root or multiple roots from which to serve files. | - |
| use | boolean | Use this middleware for every request. | false
|
| mount | string | Path from which to serve files. | /
|
| maxage | number | How long to cache file for. | 0
|
| config | StaticConfig | koa-static
configuration. | - |
import('http').ServerResponse
http.ServerResponse
(res: ServerResponse, path: string, stats: Stats) => any
SetHeaders
StaticConfig
| Name | Type | Description | Default |
| ---------- | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ |
| maxage | number | Browser cache max-age in milliseconds. | 0
|
| hidden | boolean | Allow transfer of hidden files. | false
|
| index | string | Default file name. | index.html
|
| defer | boolean | If true
, serves after return next(), allowing any downstream middleware to respond first. | false
|
| gzip | boolean | Try to serve the gzipped version of a file automatically when gzip is supported by a client and if the requested file with .gz
extension exists. | true
|
| br | boolean | Try to serve the brotli version of a file automatically when brotli is supported by a client and if the requested file with .br
extension exists (note, that brotli is only accepted over https). | true
|
| setHeaders | SetHeaders | Function to set custom headers on response. | - |
| extensions | boolean | Try to match extensions from passed array to search for file when no extension is sufficed in URL. First found is served. | false
|
For example, the below configuration will serve files from both the static
directory of the project, and the React.js dependency. When NODE_ENV
environment variable is set to production
, files will be cached for 10 days.
import { join, dirname } from 'path'
import idioCore from '@idio/core'
const STATIC = join(__dirname, 'static')
const REACT = join(dirname(require.resolve('react')), 'umd')
const DAY = 1000 * 60 * 60 * 24
const Static = async () => {
const { url } = await idioCore({
static: {
use: true,
root: [STATIC, REACT],
mount: '/scripts',
maxage: process.env.NODE_ENV == 'production' ? 10 * DAY : 0,
},
}, { port: 5004 })
return url
}
Static server started on http://localhost:5004
import('koa').Context
koa.Context
CorsOptions
| Name | Type | Description | Default |
| ------ | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------- |
| origin | string|Array|((ctx: Context) => string) | The origin or an array of origins to accept as valid. In case of an array, the origin from the request headers will be searched in the array, and if found, it will be returned (since browsers only support a single Access-Control-Allow-Origin
header). If a function is passed, it should return the string with the origin to set. If not passed, the request origin is returned, allowing any origin to access the resource. | - |
| use | boolean | Use this middleware for every request. | false
|
| config | CorsConfig | @koa/cors
configuration. | - |
CorsConfig
| Name | Type | Description | Default |
| ------------------ | ---------------------------------------- | ------------------------------------------------------ | -------------------------------- |
| origin | string | Access-Control-Allow-Origin
header value. | request Origin header
|
| allowMethods | (string | Array<string>) | Access-Control-Allow-Methods
header value. | GET,HEAD,PUT,POST,DELETE,PATCH
|
| exposeHeaders | (string | Array<string>) | Access-Control-Expose-Headers
header value. | - |
| allowHeaders | (string | Array<string>) | Access-Control-Allow-Headers
header value. | - |
| maxAge | (string | number) | Access-Control-Max-Age
header value in seconds. | - |
| credentials | boolean | Access-Control-Allow-Credentials
header value. | false
|
| keepHeadersOnError | boolean | Add set headers to err.header
if an error is thrown. | false
|
FrontendOptions
: Allows to serve front-end JS files and CSS as modules, including from node_modules folder.
| Name | Type | Description | Default |
| --------- | ----------------------------------------------- | ------------------------------------------------------- | ---------- |
| directory | (string | Array<string>) | The directory or directories from which to serve files. | frontend
|
| config | FrontendConfig | @idio/frontend
configuration. | - |
FrontendConfig
| Name | Type | Description | Default |
| ------ | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- |
| pragma | string | The pragma function to import. This enables to skip writing h
at the beginning of each file. JSX will be transpiled to have h
pragma, therefore to use React it's possible to do import { createElement: h } from 'react'
. | import { h } from 'preact'
|
Custom Middleware
When required to add any other middleware in the application not included in the @idio/core
bundle, it can be done in several ways.
- Passing the middleware function as part of the MiddlewareConfig. It will be automatically installed to be used by the Application. All middleware will be installed in order it is found in the MiddlewareConfig.
import idioCore from '@idio/core'
/** @typedef {import('koa').Middleware} Middleware */
const APIServer = async (port) => {
const { url } = await idioCore({
// 1. Add logging middleware.
/** @type {Middleware} */
async log(ctx, next) {
await next()
console.log(' --> API: %s %s %s', ctx.method, ctx.url, ctx.status)
},
// 2. Add always used error middleware.
/** @type {Middleware} */
async error(ctx, next) {
try {
await next()
} catch (err) {
ctx.status = 403
ctx.body = err.message
}
},
// 3. Add validation middleware.
/** @type {Middleware} */
async validateKey(ctx, next) {
if (ctx.query.key !== 'app-secret')
throw new Error('Wrong API key.')
ctx.body = 'ok'
await next()
},
}, { port })
return url
}
export default APIServer
Started API server at: http://localhost:5005
--> API: GET / 403
--> API: GET /?key=app-secret 200
- Passing a configuration object as part of the MiddlewareConfig that includes the
middlewareConstructor
property which will receive the reference to theapp
. Other properties such asconf
anduse
will be used in the same way as when setting up bundled middleware: settinguse
totrue
will result in the middleware being used for every request, and theconfig
will be passed to the constructor.
import rqt from 'rqt'
import idioCore from '@idio/core'
import APIServer from './api-server'
const ProxyServer = async (port) => {
// 1. Start the API server.
const API = await APIServer(5001)
console.log('API server started at %s', API)
// 2. Start a proxy server to the API.
const { url } = await idioCore({
/** @type {import('koa').Middleware} */
async log(ctx, next) {
await next()
console.log(' --> Proxy: %s %s %s', ctx.method, ctx.url, ctx.status)
},
api: {
use: true,
async middlewareConstructor(app, config) {
// e.g., read from a virtual network
app.context.SECRET = await Promise.resolve('app-secret')
/** @type {import('koa').Middleware} */
const fn = async(ctx, next) => {
const { path } = ctx
const res = await rqt(`${config.API}${path}?key=${ctx.SECRET}`)
ctx.body = res
await next()
}
return fn
},
config: {
API,
},
},
}, { port })
return url
}
API server started at http://localhost:5001
Proxy started at http://localhost:5002
--> API: GET /?key=app-secret 200
--> Proxy: GET / 200
Router Set-up
After the Application and Router instances are obtained after starting the server as the app
and router
properties of the returned object, the router can be configured to respond to custom paths. This can be done by assigning configured middleware from the map and standalone middleware, and calling the use
method on the Application instance.
import idioCore from '@idio/core'
async function pre(ctx, next) {
console.log(' <-- %s %s',
ctx.request.method,
ctx.request.path,
)
await next()
}
async function post(ctx, next) {
console.log(' --> %s %s %s',
ctx.request.method,
ctx.request.path,
ctx.response.status,
)
await next()
}
const Server = async () => {
const path = '/test'
const {
url, router, app, middleware: { bodyparser },
} = await idioCore({
// 1. Configure the bodyparser without using it for each request.
bodyparser: {
config: {
enableTypes: ['json'],
},
},
}, { port: 5003 })
// 2. Setup router with the bodyparser and path-specific middleware.
router.post(path,
pre,
bodyparser,
async (ctx, next) => {
ctx.body = {
ok: true,
request: ctx.request.body,
}
await next()
},
post,
)
app.use(router.routes())
return `${url}${path}`
}
Page available at: http://localhost:5003/test
<-- POST /test
--> POST /test 200
Copyright
Middleware icons and logo from Deco Dingbats NF font.
Middleware types descriptions by their respective authors.