@externs/idio v0.0.3
@idio/idio
@idio/idio
contains Koa's fork called Goa — web server compiled with Closure Compiler so that its source code is optimised and contains only 1 external dependency (mime-db
). Idio adds essential middleware to Goa for session, static files, CORS and compression and includes the router. As the project grows, more middleware will be added and optimised.
This is a production-ready server that puts all components together for the ease of use, while providing great developer experience using JSDoc annotations for auto-completions. Idio is not a framework, but a library that enables idiomatic usage and compilation of the server and middleware.
yarn add @idio/idio
Table Of Contents
- Table Of Contents
- API
async idio(middlewareConfig=: !MiddlewareConfig, config=: !Config): !Idio
- Static
- Session
- Custom Middleware
- Router Set-up
- Copyright & License
API
The package is available by importing its default function and named components:
import idio, { Keygrip } from '@idio/idio'
async idio( middlewareConfig=: !MiddlewareConfig,
config=: !Config,
): !Idio
Start the server. Sets the proxy
property to true
when the NODEENV is equal to _production.
- middlewareConfig !MiddlewareConfig (optional): The middleware configuration for the
idio
server. - config !Config (optional): The server configuration object.
The app can be stopped with an async .destroy
method implemented on it that closes all connections.
There are multiple items for middleware configuration:
MiddlewareConfig
extends FnMiddlewareConfig: Middleware configuration for the idio
server.
Name | Type | Description |
---|---|---|
static | StaticOptions | koa-static options. |
compress | CompressOptions | koa-compress options. |
session | SessionOptions | koa-session options. |
cors | CorsOptions | koa-cors options. |
The types for starting the server include the address, port and router configuration.
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 |
router | !_goa.RouterConfig | The configuration for the router. | - |
After the app is started, it can be accessed from the return type.
Idio
: The return type of the idio.
Name | Type | Description |
---|---|---|
url* | string | The URL on which the server was started, such as http://localhost:5000 . |
server* | !http.Server | The server instance. |
app* | !Application | The Goa application instance (with additional .destroy method). |
middleware* | !Object<string, !Middleware> | An object with configured middleware functions, which can be installed manually using app.use , or router.use . The context will be a standard Goa context with certain properties set by bundled middleware such as .session . |
router* | !_goa.Router | The router instance. |
The example below starts a simple server with session and custom middleware, which is installed (used) automatically because it's defined as a function.
const { url, app } = await idio({
// Idio's bundled middleware.
session: {
use: true,
keys: new Keygrip(['hello', 'world']),
config: {
prefix: 'example-',
},
},
// Any middleware function to be installed.
async middleware(ctx, next) {
ctx.body = 'hello world'
await next()
},
})
http://localhost:5000
hello world
Static
const { url, app } = await idio({
static: {
root: 'example', use: true,
},
// or multiple locations
static: [{
root: ['example'], use: true,
}, {
root: ['d'], use: true,
}],
})
/** http://localhost:53685/app.css */
body {
font-size: larger;
}
<!-- http://localhost:5000/em.svg -->
<xml></xml>
Content-Length: 29
Last-Modified: Thu, 18 Jul 2019 14:34:31 GMT
Cache-Control: max-age=0
Content-Type: text/css; charset=utf-8
Date: Fri, 20 Dec 2019 07:04:57 GMT
Connection: close
Content-Length: 11
Last-Modified: Thu, 18 Jul 2019 14:47:20 GMT
Cache-Control: max-age=0
Content-Type: image/svg+xml
Date: Fri, 20 Dec 2019 07:05:38 GMT
Connection: close
Session
👳♂️Explore Session Middleware Configuration
Allows to store data in the .session
property of the context. The session is serialised and placed in cookies. When the request contains the cookie, the session will be restored and validated (if signed) against the key.
const { url, app } = await idio({
session: { use: true, keys:
['hello', 'world'], config: {
signed: false,
} },
async middleware(ctx, next) {
if (ctx.session.user)
ctx.body = 'welcome back '
+ ctx.session.user
else {
ctx.session.user = 'u'
+( Math.random() * 1000).toFixed(1)
ctx.body = 'hello new user'
}
await next()
},
})
http://localhost:5000
/ hello new user
/ welcome back u190.1
Custom Middleware
When required to add any other middleware in the application not included in the Idio 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 idio from '@idio/idio' const APIServer = async (port) => { const { url } = await idio({ // 1. Add logging 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. async error(ctx, next) { try { await next() } catch (err) { ctx.status = 403 ctx.body = err.message } }, // 3. Add validation 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 idio from '@idio/idio' 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 idio({ 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('@typedefs/goa').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 { collect } from 'catchment'
import idio from '@idio/idio'
const Server = async () => {
const {
url, router, app, middleware: { pre, post, bodyparser },
} = await idio({
// 1. Configure middlewares via middlewareConstructor without installing them.
pre: {
middlewareConstructor() {
return async function(ctx, next) {
console.log(' <-- %s %s',
ctx.request.method,
ctx.request.path,
)
await next()
}
},
},
post: {
middlewareConstructor() {
return async function(ctx, next) {
console.log(' --> %s %s %s',
ctx.request.method,
ctx.request.path,
ctx.response.status,
)
await next()
}
},
},
bodyparser: {
middlewareConstructor() {
return async (ctx, next) => {
let body = await collect(ctx.req)
if (ctx.is('application/json')) {
body = JSON.parse(body)
}
ctx.request.body = body
await next()
}
},
},
}, { port: 5003 })
// 2. Setup router with the bodyparser and path-specific middleware.
router.post('/example',
pre,
bodyparser,
async (ctx, next) => {
ctx.body = {
ok: true,
request: ctx.request.body,
}
await next()
},
post,
)
app.use(router.routes())
return url
}
Page available at: http://localhost:5003
<-- POST /example
--> POST /example 200
// server response:
{ ok: true, request: { hello: 'world' } }
Copyright & License
GNU Affero General Public License v3.0
All original work on middleware and Koa are under MIT license.