Sign Up for Free

RunKit +

Try any Node.js package right in your browser

This is a playground to test code. It runs a full Node.js environment and already has all of npm’s 1,000,000+ packages pre-installed, including idio with all npm packages installed. Try it out:

var idio = require("idio")

This service is provided by RunKit and is not affiliated with npm, Inc or the package authors.

idio v2.7.0

A web-server in koa2 with MongoDB connection

idio

npm version

yarn add -E idio

idio is a koa2-based web server with some pre-installed middleware and Mongo support out of the box. Its purpose is to be able to quickly create a server with basic functionality. It also supports automatic initialisation of routes from a given directory, and watching those routes for updates (with idio-dev). This means that the whole server does not need to restart when a single route changes.

Example

/* yarn example */
import idio from 'idio'
import config, { routesConfig } from './config'

(async () => {
  try {
    const { url } = await idio(config, routesConfig)
    console.log(url) // eslint-disable-line
  } catch ({ stack }) {
    console.log(stack) // eslint-disable-line
    process.exit(1)
  }
})()
http://localhost:5000

idio(config, routesConfig?)

Idio accepts 2 arguments: the config and optional routes config to read routes from directories.

Configuration

The server can be configured using the following structure.

propertydefaultdescription
databaseURLmongodb:// localhost: 27017MongoDB connection string.
port5000Web server port.
host0.0.0.0Web server host.
autoConnecttrueWhether to automatically connect to the database server.
middlewareMiddleware configuration.
import { resolve } from 'path'

const uploadDir = resolve(__dirname, 'upload')
const routesDir = resolve(__dirname, 'routes')

const DATABASE_URL = process.env.DATABASE_URL || 'mongodb://localhost:27017/idio'
const PORT = process.env.PORT || 5000

export default {
  databaseURL: DATABASE_URL,
  port: PORT,
  middleware: {
    session: { keys: ['secret-key'] },
    multer: { config: { dest: uploadDir } },
    csrf: { },
    bodyparser: { },
    checkauth: { },
    logger: { use: true },
    koa2Jsx: { use: true, wireframe: true /*, bootstrap: true */ },
    // ---- custom middleware
    ip: {
      function(app, config) {
        return async (ctx, next) => {
          console.log(`${config.text}: ${ctx.request.ip}`)
          await next()
        }
      },
      config: { text: 'ip' },
      use: true,
    },
  },
}

Middleware Configuration

idio can instantiate some known middleware shipped with it, as well as additionally specified middleware. Each middleware property accepts the following properties:

propertydescriptiondefault
useWhether to use this middleware for every request.false
configConfiguration object expected by the middleware constructor.{}
...propsAny additional specific properties (see individual middleware configuration).

It is possible to pass multiple configurations as an array for a single type of middleware, if multiple instances of it need to be created (e.g., for static).

session

koa-session for handling sessions.

propertydescriptiondefaultrequired
keysA set of keys to be installed in app.keys-true
configkoa-session configuration{}-

multer

koa-multer for file uploads.

propertydescriptiondefaultrequired
config.destAn upload directory which will be created on start.-true
configkoa-multer configuration{}-

csrf

koa-csrf for prevention against CSRF attacks.

propertydescriptiondefaultrequired
configkoa-csrf configuration{}-

bodyparser

koa-bodyparser to parse data sent to the server.

propertydescriptiondefaultrequired
configkoa-bodyparser configuration{}-

checkauth

A simple middleware which throws if ctx.session.user is not set.

logger

koa-logger to log requests.

propertydescriptiondefaultrequired
configkoa-logger configuration{}-

compress

koa-compress to apply compression.

propertydescriptiondefaultrequired
thresholdMinimum response size in bytes to compress.1024-
configkoa-compress configuration{}-

koa2Jsx

koa2-jsx to render JSX pages.

propertydescriptiondefaultrequired
wireframeWhether to use a default wireframe.false-
bootstrapWhether to include bootstrap (when using wireframe).false-
staticWhether to use staticNodeStreamRender rather than nodeStreamRender from react package. It will strip react's metadata required for hydration. Set to this to false when client-side react is also used.true-
prettyReturn formatted HTML (as a string and not stream, therefore slower)false-
configkoa2-jsx configuration{}-

static

koa-static to serve static files.

propertydescriptiondefaultrequired
rootRoot directory as a string or directories as an array of strings.-true
mountPath from which files will be served/-
maxageControls caching time.0-
configkoa-static configuration{}-
import { resolve, dirname } from 'path'
import idio from 'idio'

const STATIC = resolve(__dirname, 'static')
const react = resolve(dirname(require.resolve('react')), 'umd')

(async () => {
  const { url } = await idio({
    middleware: {
      static: {
        use: true,
        root: ['static', react],
        mount: '/scripts',
        maxage: process.env.NODE_ENV == 'production' ?  1000 * 60 * 60 * 24 * 10 : 0,
      },
    },
  })
})

Custom Middleware

Other middleware can be passed as an object which has 3 properties:

  • function - constructor function (optional). It will receive the app and config arguments and should return a middleware function
const middlewareConstructor = async (app, config) => {
  app.context.usingFunction = true

  return async(ctx, next) => {
    await next()
    if (config.debug) {
        console.error(ctx.usingFunction)
    }
  }
}
  • config - an object that will be passed to the middleware constructor.
const config = {
  debug: process.env.NODE_DEBUG == 'idio',
}
  • use - whether to always use this middleware, which will perform app.use() after the constructor returned the middleware.

All together, setting up a custom middleware looks like this:

await idio({
  middleware: {
    customMiddleware: {
      async function(app, config) {
        app.context.usingFunction = true

        return async(ctx, next) => {
          await next()
          if (config.debug) {
              console.error(ctx.usingFunction)
          }
        }
      },
      config: { debug: process.env.NODE_DEBUG == 'idio' },
      use: true,
    },
  },
})

Router Configuration

In most cases, it is desirable to have a router for the application. idio provides a means to read files from a directory which is organised by methods (e.g., routes/get, routes/post). If the router configuration is passed to the idio function, the routes will be set up.

propertydefaultdescription
dirstringPath to the directory where routes are found. Must have get, post, etc folders inside with files which export a default module.
middleware{}A function which determines which middleware not installed with use is present for each method.
filter(s) => /\.jsx?$/.test(s)A function to filter included routes by filename.
defaultImportstrueWhether routes are exported with ES6 export default.
aliases{}A map of aliases.
export const routesConfig = {
  dir: routesDir,
  defaultImports: true,
  aliases: {
    get: {
      '/index': ['/'],
    },
  },
  middleware: {
    get: route => ['session', route],
    post: route => ['bodyparser', route],
  },
}

middleware

middleware property determines the order and installed middleware for each method. Currently it is impossible to control the middleware for each individual route. The strings returned by this function are mapped to the middleware functions created during middleware set-up earlier using the keys from the idio config. All middleware specified with this method will be run after the middleware from app configuration with use property set to true.

aliases

A map of aliases for each method, where keys are the routes, and values are arrays to which they should respond.

Routes

Routes need to be written as async Koa middleware and exported as a default function.

/* example/routes/index.js */
export default async (ctx) => {
  const n = ctx.session.views || 1
  ctx.session.views = n + 1
  ctx.body = `${n} views`
}

When not using modules, defaultImports should be set to false, otherwise no routes will be set up.

/* example/routes/index.js */
const fn = async (ctx) => {
  const n = ctx.session.views || 1
  ctx.session.views = n + 1
  ctx.body = `${n} views`
}

module.exports = fn

Connecting to Mongo

idio will try to automatically connect to the Mongo database when creating a new app. To prevent this, autoConnect option can be set to false, and a connect property will be set on the returned object, so that a connection can be established manually. This feature is useful when no database is required for a website.

const { connect } = await idio({
  databaseURL: 'mongodb://localhost:27017',
  autoConnect: false,
})
await connect()
// when no database is required
await idio({
  autoConnect: false,
})

idio-dev

To separate the server from the developer environment, idio-dev is created. It has a method to watch routes updates to run on the server, and also allows to compile JS bundles for the browser.

yarn add -D -E idio

Watching Bundles

Bundles for the client can be compiled with idio-dev using watchBundles. The method will call browserify with watchify and babelify to transpile files and put them in the required directory (which can be served by a koa-static middleware).

import { watchBundles } from 'idio-dev'

const STATIC = resolve(__dirname, 'static')
const from = resolve(__dirname, 'scripts')
const to = resolve(STATIC, 'scripts')

if (!test && !production) await watchBundles({
  from, to,
  babelify: {
    babelrc: false,
    plugins: [
      '@babel/plugin-transform-modules-commonjs',
      '@babel/plugin-proposal-object-rest-spread',
    ],
    presets: [
      '@babel/preset-react',
    ],
  },
})

Watching Routes

If you use initRoutes, for development purposes you can pass returned data to the watchRoutes function from idio-dev which allows to perform hot route reload without restarting the whole server.

import idio from 'idio'
import { watchRoutes } from 'idio-dev'

const ROUTES = resolve(__dirname, 'routes')

(async () => {
  const aliases = {
    get: {
      '/index': ['/'],
    },
  }

  const { url, app, methods, router } = await idio({
    autoConnect: false,
    middleware: {
      static: {
        function(_, c) {
          return serve(STATIC, c)
        },
        config: {
          maxage: production ? 1000 * 60 * 60 * 24 * 10 : 0,
        },
        use: true,
      },
    },
  }, {
    dir: ROUTES,
    aliases,
  })

  await watchRoutes({
    dir: ROUTES,
    methods,
    router,
    aliases,
    url,
  })
  return { app, url }
})
http://localhost:5000/ip
  src/routes/get/ip.jsx
# update routes/ip
⌁ src/routes/get/ip.jsx
> hot reloaded GET /ip
RunKit is a free, in-browser JavaScript dev environment for prototyping Node.js code, with every npm package installed. Sign up to share your code.
Sign Up for Free