Skip to content
The Core Idio Web Server Functionality And Middleware.
JavaScript
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.documentary/section-breaks
.vscode
aty
build
documentary
example
images
src
test
types
.alamoderc.json
.eslintignore
.eslintrc
.gitignore
CHANGELOG.md
LICENSE
README.md
app2.gif
package.json
test2.js
yarn.lock

README.md

@idio/core

npm version

@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

Developer-Friendly Suggestions For Middleware

Table Of Contents

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).

Session: handling sessions via cookies.

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

File Uploads: receiving files on the server.

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

Cross-Site Request Forgery: prevention against CSRF attacks.

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

Parse Body: parsing of data sent with requests.

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. -

Checking Auth: a simple function which throws if ctx.session.user is not set. Non-configurable.

CheckauthOptions

Name Type Description Default
use boolean Use this middleware for every request. false

Logging: a logger of incoming requests / response times and sizes.

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].

Compression: enabling gzip and other compression.

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. -

Static Files: serving files from filesystem.

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

CORS: return Cross-Origin Resource Sharing headers.

import('koa').Context koa.Context

CorsOptions

Name Type Description Default
origin string Array ((ctx: Context) => string)
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

Frontend: serve JS and CSS files as modules for modern browsers.

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.

  1. 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
  1. Passing a configuration object as part of the MiddlewareConfig that includes the middlewareConstructor property which will receive the reference to the app. Other properties such as conf and use will be used in the same way as when setting up bundled middleware: setting use to true will result in the middleware being used for every request, and the config 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.

Art Deco © Art Deco for Idio 2019 Idio Tech Nation Visa Tech Nation Visa Sucks

You can’t perform that action at this time.