# Documentation Conceptually **server** is a function that accepts options and other functions. The heavy lifting is already implemented **so you can focus on your project**: ```js // Import the variable into the file const server = require('server'); // All of the arguments are optional server(options, fn1, fn2, fn3, ...); ``` > You can also learn Node.js development by [following the tutorials](/tutorials). ## Getting started There's [a getting started tutorial for beginners](/tutorials/getting-started/). If you know your way around: ```bash npm install server ``` Then create some demo code in your `index.js`: ```js // Import the library const server = require('server'); // Answers to any request server(ctx => 'Hello world'); ``` Run it from the terminal: ```bash node . ``` And open your browser on [localhost:3000](http://localhost:3000/) to see it in action. ## Basic usage Some of the components are the main function on itself, [router](/documentation/router/) and [reply](/documentation/reply/). The main function accepts first an optional object for [the options](/documentation/options/), and then as many [middleware](#middleware) or arrays of middleware as wanted: ```js const server = require('server'); server({ port: 3000 }, ctx => 'Hello 世界'); ``` To use the router and reply extract their methods as needed: ```js const server = require('server'); const { get, post } = server.router; const { render, json } = server.reply; server([ get('/', ctx => render('index.hbs')), post('/', ctx => json(ctx.data)), get(ctx => status(404)) ]); ``` Then when you are splitting your files into different parts and don't have access to the global server you can import only the corresponding parts: ```js const { get, post } = require('server/router'); const { render, json } = require('server/reply'); ``` ## Middleware A *middleware* is plain function that will be called on each request. It receives [a context object](/documentation/context) and [returns a reply](/documentation/reply/), a [basic type](/documentation/reply/#return-value) or nothing. A couple of examples: ```js const setname = ctx => { ctx.user = 'Francisco'; }; const sendname = ctx => send(ctx.user); server(setname, sendname); ``` They can be placed as `server()` arguments, combined into an array or imported/exported from other files: ```js server( ctx => send(ctx.user), [ ctx => console.log(ctx.data) ], require('./comments/router.js') ); ``` Then in `./comments/router.js`: ```js const { get, post, put, del } = require('server/router'); const { json } = require('server/reply'); module.exports = [ get('/', ctx => { /* ... */ }), post('/', ctx => { /* ... */ }), put('/:id', ctx => { /* ... */ }), del('/:id', ctx => { /* ... */ }), ]; ``` The main difference between synchronous and asynchronous functions is that you use `async` keyword to then be able to use the keyword `await` within the function, avoiding [callback hell](http://callbackhell.com/). Some examples of middleware: ```js // Some simple logging const mid = () => { console.log('Hello 世界'); }; // Asynchronous, find user with Mongoose (MongoDB) const mid = async ctx => { ctx.user = await User.find({ name: 'Francisco' }).exec(); console.log(ctx.user); }; // Make sure that there is a user const mid = ctx => { if (!ctx.user) { throw new Error('No user detected!'); } }; // Send some info to the browser const mid = ctx => { return `Some info for ${ctx.user.name}`; }; ``` In this way you can `await` inside of your function. Server.js will also await to your middleware before proceeding to the next one: ```js server(async ctx => { await someAsyncOperation(); console.log('I am first'); }, ctx => { console.log('I am second'); }); ``` If you find an error in an async function you can throw it. It will be caught, a 500 error will be displayed to the user and the error will be logged: ```js const middle = async ctx => { if (!ctx.user) { throw new Error('No user :('); } }; ```
**Avoid callback-based functions**: error propagation is problematic and they have to be converted to promises. Strongly prefer an async/await workflow.
## Express middleware Server.js is using express as the underlying library (we <3 express!). You can import middleware designed for express with `modern`: ```js const server = require('server'); // Require it and initialize it with some options const legacy = require('helmet')({ ... }); // Convert it to server.js middleware const mid = server.utils.modern(legacy); // Add it as you'd add a normal middleware server(mid, ...); ``` > Note: the `{ ... }` represent the options for that middleware since many of [express libraries](https://expressjs.com/en/guide/writing-middleware.html) follow the [factory pattern](https://github.com/expressjs/express/issues/3150). To simplify it, we can also perform this operation inline: ```js const server = require('server'); const { modern } = server.utils; server( modern(require('express-mid-1')({ ... })), modern(require('express-mid-2')({ ... })), // ... ); ``` Or just keep the whole middleware in a separated file/folder: ```js // index.js const server = require('server'); const middleware = require('./middleware'); const routes = require('./routes'); server(middleware, routes); ``` Then in our `middleware.js`: ```js // middleware.js const server = require('server'); const { modern } = server.utils; module.exports = [ modern(require('express-mid-1')({ /* ... */ })), modern(require('express-mid-2')({ /* ... */ })) ]; ``` Read the next section for a great example of a common middleware from express used with server. ## CORS To allow requesting a resource from another domain you must enable [Cross-Origin Resource Sharing (CORS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). To do so, you have two options: do it manually or through a great library. Both of them end up setting some headers. Let's see how to do it manually for any domain: ```js const server = require('server'); const { header } = server.reply; // OR server.reply; const cors = [ ctx => header("Access-Control-Allow-Origin", "*"), ctx => header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"), ctx => header("Access-Control-Allow-Methods", "GET, PUT, PATCH, POST, DELETE, HEAD"), ctx => ctx.method.toLowerCase() === 'options' ? 200 : false ]; server({}, cors, ...); ``` If you want to whitelist some domains it's not easy manually, so we can use the great package [`cors` from npm](https://www.npmjs.com/package/cors): ```js const server = require('server'); // Load it with the options const corsExpress = require('cors')({ origin: ['https://example.com', 'https://example2.com'] }); // Make the express middleware compatible with server const cors = server.utils.modern(corsExpress); // Launch the server with this specific middleware server({}, cors, ...); ``` ## Routing This is the concept of redirecting each request to our server to the right place. For instance, if the user requests our homepage `/` we want to render the homepage, but if they request an image gallery `/gallery/67546` we want to render the gallery `67546`. For this we will be creating routes using server's routers. We can import it like this: ```js const server = require('server'); const { get, post } = server.router; // OR const { get, post } = require('server/router'); ``` There are some other ways, but these are the recommended ones. Then we say the path of the request for the method that we want to listen to and a middleware: ```js const getHome = get('/', () => render('index.pug')); const getGallery = get('/gallery/:id', async ctx => { const images = await db.find({ id: ctx.params.id }).exec(); return render('gallery.pug', { images }); }); ``` Let's put it all together to see how they work: ```js const server = require('server'); const { get, post } = server.router; const getHome = get('/', () => render('index.pug')); const getGallery = get('/gallery/:id', async ctx => { const images = await db.find({ id: ctx.params.id }).exec(); return render('gallery.pug', { images }); }); server(getHome, getGallery); ``` We can also receive `post`, `del`, `error`, `socket` and other request types through the router. To see them all, visit the Router documentation: Router Documentation ## Advanced topics There is a lot of basic to mid-difficulty documentation to do until we even get here. Just a quick note so far: The main function returns a promise that will be fulfilled when the server is running and can be accessed. It will receive a more primitive context. So this is perfectly valid: ```js server(ctx => 'Hello world').then(app => { console.log(`Server launched on http://localhost:${app.options.port}/`); }); ``` If you need to stop the server manually, you can do so by invoking the `.close()` function: ```js server(ctx => 'Hello world').then(async app => { console.log('Launched'); await app.close(); console.log('Closed'); }); ``` # Options Available options, their defaults, types and names in `.env`: |name |default |[.env](#environment) |type | |-----------------------|-------------------|--------------------------|------| |[`port`](#port) |`3000` |`PORT=3000` |Number| |[`secret`](#secret) |`'secret-XXXX'` |`SECRET=secret-XXXX` |String| |[`public`](#public) |`'public'` |`PUBLIC=public` |String| |[`views`](#views) |`'views'` |`VIEWS=views` |String| |[`engine`](#engine) |`'pug'` |`ENGINE=pug` |String| |[`env`](#env) |`'development'` |**`NODE_ENV=development`**|String| |[`favicon`](#favicon) |`false` |`FAVICON=public/logo.png` |String| |[`parse`](#parse) |[[info]](#parse) |[[info]](#parse) |Object| |[`session`](#session) |[[info]](#session) |[[info]](#session) |Object| |[`socket`](#socket) |[[info]](#socket) |[[info]](#socket) |Object| |[`security`](#security)|[[info]](#security)|[[info]](#security) |Object| |[`log`](#log) |`'info'` |`LOG=info` |String| You can set those through **the first argument** in `server()` function: ```js // Import the main library const server = require('server'); // Launch the server with the options server({ port: 3000, public: 'public', }); ``` The options preference order is this, from more important to less: 1. `.env`: the variable [within the environment](#environment). 2. `server({ OPTION: 3000 })`: the variable [set as a parameter](#parameter) when launching the server. 3. *defaults*: defaults will be used as can be seen below They are accessible for your dev needs through `ctx.options` ([read more in context options](/documentation/context/#options)): ```js server(ctx => console.log(ctx.options)); // { port: 3000, public: './public', ... } ``` ### Environment Environment variables are *not commited in your version control* but instead they are provided by the machine or Node.js process. In this way these options can be different in your machine and in testing, production or other type of servers. They are uppercase and they can be set through a file called literally `.env` in your root folder: ``` PORT=3000 PUBLIC=public SECRET=secret-XXXX ENGINE=pug NODE_ENV=development ``` > Remember to **add `.env` to your `.gitignore`**. To set them in remote server it will depend on the hosting that you use ([see Heroku example](https://devcenter.heroku.com/articles/config-vars)). ### Argument The alternative to the environment variables is to pass them **as the first argument** when calling `server()`. Each option is a combination of key/value in the object and they all go in lowercase. See some options with their defaults: ```js const server = require('server'); server({ port: 3000, public: 'public', secret: 'secret-XXXX', engine: 'pug', env: 'development' // Remember this is "env" and not "node_env" here }); ``` ### Special cases As a general rule, an option that is an object becomes a `_` separated string in uppercase for the `.env` file. For example, for the SSL we have to pass an object such as: ```js server({ port: 3000, ssl: { key: './ssl.pem', cert: './ssl.cert' } }); ``` So if we want to put this in the environment variable we'd set it up such as: ``` PORT=3000 SSL_KEY=test/fixtures/keys/agent2-key.pem SSL_CERT=test/fixtures/keys/agent2-cert.cert ``` The converse is not true; a `_` separated string in the `.env` does not necessarily become an object as a parameter. You'll have to read the documentation of each option and plugin for the specific details. ## Port The port where you want to launch the server. Defaults to `process.env.PORT` or `3000` if not found, and it's the only option that can be specified as a single option: ```js server(); // Use the default port 3000 server(3000); // Specify the port server({ port: 3000 }); // The same as the previous one ``` If you are setting the port in your environment that will take preference over the argument [as all environment variables](#environment). So it will work seamlessly in Heroku and other hosts that define a `PORT` environment variable. Or you can leave it empty and just use your `.env` file: ``` PORT=3000 ``` Example: setting the port to some other number. For numbers 1-1024 you'd need administrator permission, so we're testing it with higher ports: ```js const options = { port: 5001 }; /* test */ const same = ctx => ({ port: ctx.options.port }); const res = await run(options, same).get('/'); expect(res.body.port).toBe(5001); ``` ## Secret It is [**highly recommended**](https://serverjs.io/tutorials/sessions-production/) that you set this in your environment variable for both development and production before you start coding. It should be a random and long string. It can be used by middleware for storing secrets and keeping cookies/sessions: ``` SECRET=your-random-string-here ``` The *default* provided will be different each time the server is launched. This is not suitable for production, since you want persistent sessions even with server restarts. See the [session in production tutorial](https://serverjs.io/tutorials/sessions-production/) to set it up properly (includes some extras such as Redis sessions). It **cannot** be set as a variable: `server({ secret: 'whatever' });`. If you have other secrets it is recommended that you prepend those by their respective vendor names: ```js # Used by server.js for sessions SECRET=your-random-string-here # Used by different middleware GITHUB_SECRET=your-github-secret CLOUDINARY_SECRET=your-cloudinary-secret # ... ``` ## Public |name |default |[.env](#environment) |type |notes | |---------|---------|---------------------|-------|------------| |`public` |`public` |`PUBLIC=public` |String |Folder path | The folder where your **static assets** are. This includes images, styles, javascript for the browser, etc. Any file that you want directly accessible through the browser such as `example.com/myfile.pdf` should be in this folder. You can set it to any folder within your project. To set the public folder in the environment add this to [your `.env`](#environment): ``` PUBLIC=public ``` Through the initialization parameter: ```js const options = { public: 'public' }; /* test */ const same = ctx => ({ public: ctx.options.public }); const res = await run(options, same).get('/'); expect(res.body.public).toBe(path.join(process.cwd() + '/public')); ``` To set the root folder specify it as `'./'`: ```js const options = { public: './' }; /* test */ const same = ctx => ({ public: ctx.options.public }); const res = await run(options, same).get('/'); expect(res.body.public).toBe(process.cwd() + path.sep); ``` If you don't want any of your files to be accessible publicly, then you can cancel it through a false or empty value: ```js server({ public: false }); server({ public: '' }); ``` ## Views |name |default |[.env](#environment) |type |notes | |---------|---------|---------------------|-------|------------| |`views` |`views` |`VIEWS=views` |String |Folder path | The folder where you put your view files, partials and templates. These are the files used by the [`render()` method](/documentation/reply/#render-). You can set it to any folder within your project. It walks the given directory so please make sure not to include the e.g. root directory since then it'll attempt to walk `node_modules` and that might delay the time to to launch the server significantly. To set the views folder in the environment add this to [your `.env`](#environment): ``` VIEWS=views ``` Or pass it as another option: ```js const options = { views: 'views' }; /* test */ const same = ctx => ({ views: ctx.options.views }); const res = await run(options, same).get('/'); expect(res.body.views).toBe(path.join(process.cwd(), 'views') + path.sep); ``` You can set it to any folder, like `./templates`: ```js const options = { views: './templates' }; /* test */ options.views = './test/views'; const same = ctx => ({ views: ctx.options.views }); const res = await run(options, same).get('/'); expect(res.body.views).toBe(process.cwd() + path.sep + 'test/views' + path.sep); ``` If you don't have any view file you don't have to create the folder. The files within `views` should all have an extension such as `.hbs`, `.pug`, etc. To see how to install and use those keep reading. ## Engine |name |default |[.env](#environment) |type |notes | |---------|---------|---------------------|---------------|------------| |`engine` |`engine` |`ENGINE=engine` |String, Object |[engine](https://github.com/expressjs/express/wiki#template-engines) | > Note: this option, as all options, can be ignored and server.js will work with both `.pug` and `.hbs` (Handlebars) file types. The view engine that you want to use to render your templates. [See all the available engines](https://github.com/expressjs/express/wiki#template-engines). To use an engine you normally have to install it first except for the pre-installed ones [pug](https://pugjs.org/) and [handlebars](http://handlebarsjs.com/): ``` npm install [ejs|nunjucks|emblem] --save ``` Then to use that engine you just have to add the extension to the [`render()` method](/documentation/reply/#render-): ```js // No need to specify the engine if you are using the extension server(ctx => render('index.pug')); server(ctx => render('index.hbs')); // ... ``` However if you want to use it without extension, you can do so by specifying the engine in `.env`: ``` ENGINE=pug ``` Or through the corresponding option in javascript: ```js server({ engine: 'pug' }, ctx => render('index')); ``` The files will be relative to your `views` folder. When using `hbs`, the `views` folder will also be used to load your partials, so you can write them like this: ```html {{> head title="Hello world" }} {{> partials/nav }} ... ``` ### Writing your own engine Engines are really easy to write with server.js. They must be a function that receives the file path and the options (or locals) and returns the text to render from the engine. It can be either sync or async. To configure it for handling a specific extension, just put that as the key in an object for `engine`. As an example of how to handle nunjucks, in a single file for it: ```js // nunjucks-engine.js const nunjucks = require('nunjucks'); // The .render() in Nunjucks is sync, so no need to wait module.exports = (file, options) => nunjucks.render(file, options); ``` Then in your main file: ```js const server = require('server'); const { get } = server.router; const { render } = server.reply; const nunjucks = require('./nunjucks'); const options = { engine: { // Register two keys for the same render function nunjucks, njk: nunjucks } }; server(options, [ get('/', () => render('index.njk', { a: 'b' })), get('/hello', () => render('hello.nunjucks')) ]); ``` You can also set the function to `async` and it will wait until it is resolved, and return the result to the browser as expected. ## Env |name |default |[.env](#environment) |type |notes | |------|--------------|---------------------------|---------------|---------------------| |`env` |`development` |**`NODE_ENV=development`** |String, Object |['development', 'test', 'production'] | Define the context in which the server is running. It **has to be** one of these: `'development'`, `'test'` or `'production'`. Some functionality might vary depending on the environment, such as live/hot reloading, cache, etc. so it is recommended that you set these appropriately. > Note: The environment variable is called **NODE_ENV** while the option as a parameter is **env**. This variable does not make sense as a parameter to the main function, so we'll normally use this within our `.env` file. See it here with the *default `development`*: ``` NODE_ENV=development ``` Then in your hosting environment you'd set it to production (some hosts like Heroku do so automatically): ``` NODE_ENV=production ``` These are the only accepted types for NODE_ENV: ```js development test production ``` You can check those within your code like: ```js server(ctx => { console.log(ctx.options.env); }); ``` ## Favicon To include a favicon, specify its path with the `favicon` key: ```js const server = require('server'); server({ favicon: 'public/favicon.png' }, ctx => 'Hello world' ); ``` The path can be absolute or relative to the root of your project. Most browsers require `/favicon.ico` automatically, so you might be seeing 404 errors if the favicon is not returned for this situation. ## Parse The parsing middleware is included by default. It uses few of them under the hood and these are the options for all of them. They should all work by default, but still give access to the options if you want to make some more advanced modifications. ### Body parser This is the name for the default parser for `
` without anything else. The technical name and for those coming from express is `urlencoded`. See the [available options in the middleware documentation](https://github.com/expressjs/body-parser#bodyparserurlencodedoptions). As an example, let's say that you want to upgrade from the default `limit` of `100kb` to `1mb`: ```js server({ parser: { body: { limit: '1mb' } } }); ``` ### JSON parser This will parse JSON requests into the actual variables. See the [available options in this middleware documentation](https://github.com/expressjs/body-parser#bodyparserjsonoptions). As an example, let's say (as above) that we want to change the limit for requests from `100kb` to `1mb`. To do so, change the json parser option: ```js server({ parser: { json: { limit: '1mb' } } }); ``` You can also combine the two above: ```js server({ parser: { body: { limit: '1mb' }, json: { limit: '1mb' } } }); ``` ### Text parser Plain ol' text. As with the other examples, refer to the [middleware full documentation](https://github.com/expressjs/body-parser#bodyparsertextoptions) for more comprehensive docs. An example, setting the size limit for the requests: ```js server({ parser: { text: { limit: '1mb' } } }); ``` ### Data parser This is for file uploads of any type. It uses Formidable underneath, so refer to [the Formidable documentation](https://github.com/felixge/node-formidable#api) for the full list of options. An example: ```js server({ parser: { data: { uploadDir: '/my/dir' } } }); ``` ### Cookie parser For using cookies, it uses cookie-parser underneath so [refer to express documentation](https://expressjs.com/en/api.html#res.cookie) for the full list of options. An example: ```js server({ parser: { cookie: { maxAge: 900000, httpOnly: true } } }); ``` ## Session It accepts these options as an object: ```js server({ session: { resave: false, saveUninitialized: true, cookie: {}, secret: 'INHERITED', store: undefined, redis: undefined }}); ``` You can [read more about these options in Express' package documentation](https://github.com/expressjs/session). All of them are optional. Secret will inherit the secret from the global secret if it is not explicitly set. If the session.redis option or the env `REDIS_URL` is set with a Redis URL, a Redis store will be launched to achieve persistence in your sessions. Read more about this in [the tutorial **Sessions in production**](http://serverjs.io/tutorials/sessions-production/). Example: ```bash # .env REDIS_URL=redis://:password@hostname:port/db_number ``` ```js // index.js const server = require('server'); // It will work by default since it's an env variable server({}, ...); ``` Otherwise, to pass it manually (**not recommended**) pass it through the options: ```js const redis = 'redis://:password@hostname:port/db_number'; server({ session: { redis } }, ...); ``` ### Session Stores To use one of the many available third party session stores, pass it as the `store` parameter: ```js // Create your whole store thing const store = ...; // Use it within the session server({ session: { store } }, ...); ``` Many of the stores will need you to pass the raw **`session`** initially like this: ```js const RedisStore = require('connect-redis')(session); const store = RedisStore({ ... }); ``` You can access this variable through `server.session` after requiring `server`: ```js const server = require('server'); const RedisStore = require('connect-redis')(server.session); const store = RedisStore({ ... }); server({ session: { store } }, ...); ``` ## Socket You can pass here the [options for socket.io](https://socket.io/docs/server-api/#new-Server-httpServer-options): ```js server({ socket: { path: '/custompath' } }); ``` This is the equivalent of doing this with socket.io: ```js const io = socket(server, { path: '/custompath' }); ``` You can see an example on how it's used [in the *websocket example*](https://github.com/franciscop/server/blob/master/examples/websocket/index.js). ## Security It combines [Csurf](https://github.com/expressjs/csurf) and [Helmet](https://github.com/helmetjs/helmet) to give extra security: ```js server({ security: { csrf: { ignoreMethods: ['GET', 'HEAD', 'OPTIONS'], value: req => req.body.csnowflakerf }, frameguard: { action: 'deny' } } }); ``` We are using [Helmet](https://helmetjs.github.io/) for great security defaults. To pass any [helmet option](https://github.com/helmetjs/helmet), just pass it as another option in security: ```js server({ security: { frameguard: { action: 'deny' } } }); ``` For quick tests/prototypes, the whole security plugin can be disabled (**not recommended**): ```js server({ security: false }); ``` Individual parts can also be disabled like this. This makes sense if you use other mechanisms to avoid CSRF, such as JWT: ```js server({ security: { csrf: false } }); ``` Their names in the `.env` are those: ``` SECURITY_CSRF SECURITY_CONTENTSECURITYPOLICY SECURITY_EXPECTCT SECURITY_DNSPREFETCHCONTROL SECURITY_FRAMEGUARD SECURITY_HIDEPOWEREDBY SECURITY_HPKP SECURITY_HSTS SECURITY_IENOOPEN SECURITY_NOCACHE SECURITY_NOSNIFF SECURITY_REFERRERPOLICY SECURITY_XSSFILTER ``` ## Log Display some data that might be of value for the developers. This includes from just some information up to really important bugs and errors notifications. You can set [several log levels](https://www.npmjs.com/package/log#log-levels) and it **defaults to 'info'**: - `emergency`: system is unusable - `alert`: action must be taken immediately - `critical`: the system is in critical condition - `error`: error condition - `warning`: warning condition - `notice`: a normal but significant condition - `info`: a purely informational message - `debug`: messages to debug an application Do it either in [your `.env`](#environment): ``` LOG=info ``` Or as a parameter to the main function: ```js server({ log: 'info' }); ``` To use it do it like this: ```js server(ctx => { ctx.log.info('Simple info message'); ctx.log.error('Shown on the console'); }); ``` If we want to modify the level and only show the warnings or more important logs: ```js server({ log: 'warning' }, ctx => { ctx.log.info('Not shown anymore'); ctx.log.error('Shown on the console'); }); ``` ### Advanced logging You can also pass a `report` variable, in which case the level should be specify as `level`: ```js server({ log: { level: 'info', report: (content, type) => { console.log(content); } } }); ``` This allows you for instance to handle some specific errors in a different way. It is also useful for testing that the correct data is printed on the console in certain situations. # Context Context is the **only** parameter that middleware receives and contains all the information available at this point of the request: |name |example |type | |----------------------|----------------------------------------------|--------| |[.options](#-options) | `{ port: 3000, public: 'public' }` |Object | |[.data](#-data) | `{ firstName: 'Francisco '}` |Object | |[.params](#-params) | `{ id: 42 }` |Object | |[.query](#-query) | `{ search: '42' }` |Object | |[.session](#-session) | `{ user: { firstName: 'Francisco' } }` |Object | |[.headers](#-headers) | `{ 'Content-Type': 'application/json' }` |Object | |[.cookies](#-cookies) | `{ acceptCookieLaw: true }` |Object | |[.files](#-files) | `{ profilepic: { ... } }` |Object | |[.ip](#-ip) | `'192.168.1.1'` |String | |[.url](#-url) | `'/cats/?type=cute'` |String | |[.method](#-method) | `'GET'` |String | |[.path](#-path) | `'/cats/'` |String | |[.secure](#-secure) | `true` |Boolean | |[.xhr](#-xhr) | `false` |Boolean | It can appear at several points, but the most important one is as a middleware parameter: ```js // Load the server from the dependencies const server = require('server'); // Display "Hello 世界" for any request const middleware = ctx => { // ... (ctx is available here) return 'Hello 世界'; }; // Launch the server with a single middleware server(middleware); ``` ## .options An object containing [all of the parsed options](/documentation/options/) used by server.js. It combines environment variables and explicit options from `server({ a: 'b' });`: ```js const mid = ctx => { expect(ctx.options.port).toBe(3012); }; /* test */ const res = await run({ port: 3012 }, mid, () => 200).get('/'); expect(res.status).toBe(200); ``` If we have a variable set in the `.env` or through some other environment variables, it'll use that instead as [environment options take preference](/documentation/options/): ```bash # .env PORT=80 ``` ```js const mid = ctx => { expect(ctx.options.port).toBe(7693); }; /* test */ const res = await run({ port: 7693 }, mid, () => 200).get('/'); expect(res.status).toBe(200); ``` ## .data This is aliased as `body` as in other libraries. It is the data sent with the request. It can be part of a POST or PUT request, but it can also be set by others such as websockets: ```js const middle = ctx => { expect(ctx.data).toBe('Hello 世界'); }; // Test it (csrf set to false for testing purposes) run(noCsrf, middle).post('/', { body: 'Hello 世界' }); run(middle).emit('message', 'Hello 世界'); ``` To handle forms sent normally: ```pug //- index.pug form(method="POST" action="/contact") input(name="email") input(name="_csrf" value=csrf type="hidden") input(type="submit" value="Subscribe") ``` Then to parse the data from the back-end: ```js const server = require('server'); const { get, post } = server.router; const { render, redirect } = server.reply; server([ get(ctx => render('index.pug')), post(ctx => { console.log(ctx.data); // Logs the email return redirect('/'); }) ]); ``` ## .params Parameters from the URL as specified [in the route](/documentation/router/): ```js const mid = get('/:type/:id', ctx => { expect(ctx.params.type).toBe('dog'); expect(ctx.params.id).toBe('42'); }); // Test it run(mid).get('/dog/42'); ``` They come from parsing [the `ctx.path`](#-path) with the [package `path-to-regexp`](https://www.npmjs.com/package/path-to-regexp). Go there to see more information about it. ```js const mid = del('/user/:id', ctx => { console.log('Delete user:', ctx.params.id); }); ``` ## .query The parameters from the query when making a request. These come from the url fragment `?answer=42&...`: ```js const mid = ctx => { expect(ctx.query.answer).toBe('42'); expect(ctx.query.name).toBe('Francisco'); }; // Test it run(mid).get('/question?answer=42&name=Francisco'); ``` ## .session After following the [sessions in production tutorial](localhost:3000/tutorials/sessions-production/), sessions should be ready to get rolling. This is an object that persist among the user refreshing the page and navigation: ```js // Count how many pages the visitor sees const mid = ctx => { ctx.session.counter = (ctx.session.counter || 0) + 1; return ctx.session.counter; }; // Test that it works run(ctx).alive(async ctx => { await api.get('/'); await api.get('/'); const res = await api.get('/'); expect(res.body).toBe('3'); }); ``` ## .headers Get the headers that were sent with the request: ```js const mid = ctx => { expect(ctx.headers.answer).toBe(42); }; // Test it run(mid).get('/', { headers: { answer: 42 } }); ``` ## .cookies Object that holds the cookies sent by the client: ```js const mid = ctx => { console.log(ctx.cookies); }; run(mid).get('/'); ``` ## .files Contains any and all of the files sent by a request. It would normally be sent through a form with an `` field or through a [`FormData` in front-end javascript](https://developer.mozilla.org/en-US/docs/Web/API/FormData): ```html
``` Note the [csrf token](/documentation/router/#csrf-token) and the [`enctype="multipart/form-data"`](https://stackoverflow.com/q/1342506/938236), both of them needed. Then to handle it with Node.js: ```js const mid = post('/profilepic', ctx => { // This comes from the "name" in the input field console.log(ctx.files.profilepic); return redirect('/profile'); }); ``` ## .ip The IP of the client. If your server is running behind a proxy, it uses [the de-facto standard `x-forwarded-for` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For) to get the right client IP: ```js const mid = ctx => { console.log(ctx.ip); }; run(mid).get('/'); ``` It can be useful with services like `geoip-lite` to find the user's location: ```js // Localize user depending on their IP const geoip = require('geoip-lite'); module.exports = ctx => { ctx.geo = geoip.lookup(ctx.ip); }; // { // range: [ 3531655168, 3531657215 ], // country: 'JP', // region: '24', // eu: '0', // timezone: 'Asia/Tokyo', // city: 'Yokkaichi', // ll: [ 34.9667, 136.6167 ], // metro: 0, // area: 50 // } ``` ## .url The full cuantified URL: ```js const mid = ctx => { expect(ctx.url).toBe('/hello?answer=42'); }; run(mid).get('/hello?answer=42'); ``` ## .method The request method, it can be `GET`, `POST`, `PUT`, `DELETE`: ```js const mid = ctx => { expect(ctx.method).toBe('GET'); }; // Test it run(mid).get('/'); ``` Or other methods: ```js const mid = ctx => { expect(ctx.method).toBe('POST'); }; // Test it run(noCsrf, mid).post('/'); ``` ## .path Only the path part from the URL. It is the full URL except for the query: ```js const mid = ctx => { expect(ctx.path).toBe('/question'); }; // Test it run(mid).get('/question?answer=42'); ``` ## .secure Returns true if the request is made through HTTPS. Take into account that if you are behind Cloudflare or similar it might be reported as false even though your clients see `https`: ```js const mid = ctx => { expect(ctx.secure).toBe(false); }; // Test it run(mid).get('/'); ``` ## .xhr A boolean set to true if the request was done through AJAX. Specifically, if `X-Requested-With` is `“XMLHttpRequest”`: ```js const mid = ctx => { expect(mid.xhr).toBe(false); }; run(mid).get('/'); ``` # Router Available methods and their parameters for `server.router`: |route name |example | |-------------------------------------------|---------------------------------| |[`get(PATH, FN1, FN2, ...)`](#get-) |`get('/', ctx => { ... })` | |[`head(PATH, FN1, FN2, ...)`](#head-) |`head('/', ctx => { ... })` | |[`post(PATH, FN1, FN2, ...)`](#post-) |`post('/', ctx => { ... })` | |[`put(PATH, FN1, FN2, ...)`](#put-) |`put('/', ctx => { ... })` | |[`del(PATH, FN1, FN2, ...)`](#del-) |`del('/', ctx => { ... })` | |[`error(NAME, FN1, FN2, ...)`](#error-) |`error('user', ctx => { ... })` | |[`sub(SUBDOMAIN, FN1, FN2, ...)`](#sub-) |`sub('es', ctx => { ... })` | |[`socket(NAME, FN1, FN2, ...)`](#socket-) |`socket('/', ctx => { ... })` | A router is a function that tells the server how to handle each request. They are a specific kind of middleware that wraps your logic and acts as a gateway: ```js // Import methods 'get' and 'post' from the router const { get, post } = require('server/router'); server([ get('/', ctx => { /* ... */ }), // Render homepage get('/users', ctx => { /* ... */ }), // GET requests to /users post('/users', ctx => { /* ... */ }) // POST requests to /users ]); ``` The `ctx` argument is [explained in middleware's Context](/documentation/context). The router methods can be imported in several ways: ```js // For whenever you have previously defined `server` const { get, post } = server.router; // For standalone files: const { get, post } = require('server/router'); ``` There are many more ways of importing the router methods, but those above are the recommended ones. ### Complex routers If you are going to have many routes, we recommend splitting them into separated files, either in the root of the project as `routes.js` or in a different place: ```js // app.js const server = require('server'); const routes = require('./routes'); server(routes); ``` ```js // routes.js const { get, post } = require('server/router'); const ctrl = require('auto-load')('controllers'); // You can simply export an array of routes module.exports = [ get('/', ctrl.home.index), get('/users', ctrl.users.index), post('/users', ctrl.users.add), get('/photos', ctrl.photos.index), post('/photos', ctrl.photos.add), ... ]; ``` The `ctx` variable is [the context (documentation here)](https://serverjs.io/documentation/context). One important difference between the routes and middleware is that [**all routes are final**](#routes-are-final). This means that **each request will use one route at most**. All of the routers reside within the `server.router` and follow this structure: ```js const server = require('server'); const { TYPE } = server.router; const doSomething = TYPE(ID, fn1, [fn2], [fn3]); server(doSomething); ``` ### CSRF token For POST, PUT and DELETE requests a valid [**CSRF** token](https://github.com/expressjs/csurf) with the field name of `_csrf` must be sent as well. The local variable is set by server.js so you can include it like this: ```html
``` If you are using an API from Javascript, such as the new `fetch()` you can handle it this way: ```html ``` ```js // Within your javascript.js/bundle.js/app.js fetch('/', { method: 'POST', body: 'hello world', credentials: 'include', // Important! to maintain the session headers: { 'csrf-token': csrf } // From 'window' }).then(...); ``` Or you could also just disable it if you know what you are doing: ```js server({ security: { csrf: false } }, ...); ``` ## get() Handle requests of the type `GET` (loading a webpage): ```js // Create a single route for GET / const route = get('/', ctx => 'Hello 世界'); // Testing that it actually works run(route).get('/').then(res => { expect(res.body).toBe('Hello 世界'); }); ``` > Note: Read more about the [tests in code examples](/documentation/testing/#code) or just ignore them. You can specify a query and param to be set: ```js const route = get('/:page', ctx => { console.log(ctx.params.page); // hello console.log(ctx.query.name); // Francisco return { page: ctx.params.page, name: ctx.query.name }; }); // Test it run(route).get('/hello?name=Francisco').then(res => { expect(res.body).toEqual({ page: 'hello', name: 'Francisco' }); }); ``` ## head() Handle requests of the type `HEAD`, which never contain a body: ```js // Create a single route for GET / const route = head('/', ctx => 'Hello 世界'); // Testing that it actually works run(route).head('/').then(res => { // Body is empty expect(res.body).toBe(''); }); ``` ## post() Handle requests of the type `POST`. It needs [a csrf token](#csrf-token) to be provided: ```js // Create a single route for POST / const route = post('/', ctx => { console.log(ctx.data); }); // Test our route. Note: csrf disabled for testing purposes run(noCsrf, route).post('/', { body: 'Hello 世界' }); ``` The [`data` property](/documentation/context/#data) can be a string or a simple object of `{name: value}` pairs. Example: ```js // index.js const server = require('server'); const { get, post } = server.router; const { file, redirect } = server.reply; server( get('/', ctx => file('index.hbs')), post('/', ctx => { // Show the submitted data on the console: console.log(ctx.data); return redirect('/'); }) ); ``` ```html

Contact us

``` Example 2: JSON API. To POST with JSON you can follow this: ```js fetch('/42', { method: 'PUT', body: JSON.stringify({ a: 'b', c: 'd' }), credentials: 'include', // !important for the CSRF headers: { 'csrf-token': csrf, 'Content-Type': 'application/json' } }).then(res => res.json()).then(item => { console.log(item); }); ``` ## put() Handle requests of the type "PUT". It needs [a csrf token](#csrf-token) to be provided: ```js // Create a single route for PUT /ID const route = put('/:id', ctx => { console.log(ctx.params.id, ctx.data); }); // Test our route. Note: csrf disabled for testing purposes run(noCsrf, route).put('/42', { body: 'Hello 世界' }); ``` The HTML `
` does not support `method="PUT"`, however we can overcome this by adding a special field called `_method` to the query: ```html ...
``` For Javascript you can just set it to `method`, for example using the new API `fetch()`: ```js fetch('/42', { method: 'PUT', body: 'whatever', credentials: 'include', // !important for the CSRF headers: { 'csrt-token': csrf } }); ``` ## del() Handle requests of the type "DELETE". It needs [a csrf token](#csrf-token) to be provided: ```js // Create a single route for DELETE /ID const route = del('/:id', ctx => { console.log(ctx.params.id); }); // Test our route. Note: csrf disabled for testing purposes run(noCsrf, route).del('/42'); ``` The HTML `
` does not support `method="DELETE"`, however we can overcome this by adding a special field called `_method` to the query: ```html ...
``` For Javascript you can just set it to `method`, for example using the new API `fetch()`: ```js fetch('/42', { method: 'DELETE', credentials: 'include', // !important for the CSRF headers: { 'csrt-token': csrf } }); ``` ## error() It handles an error thrown by a previous middleware: ```js const handle = error('special', ctx => { console.log(ctx.error); }); // Test it. First let's define our error in a middleware: const throwsError = ctx => { const err = new Error('This is a test error'); err.code = 'special'; throw err; }; // Then test it faking a request run(throwsError, handle).get('/'); ``` It accepts an optional name and then middleware. If there's no name, it will catch all of the previously thrown errors. The name will match the **beginning** of the string name, so you can split your errors by domain: ```js // This will be caught since 'user' === 'user' const mid1 = ctx => { const err = new Error('No username detected'); err.code = 'user.noname'; throw err; }; // This will be caught since 'user.noname' begins by 'user' const mid2 = ctx => { const err = new Error('No username detected'); err.code = 'user.noname'; throw err; }; const handleUser = error('user', ctx => { console.log(ctx.error); }); server(mid1, mid2, handleUser); ``` ## sub() Handle subdomain calls: ```js const server = require('server'); const { sub } = server.router; server([ sub('es', ctx => { console.log('Call to subdomain "es"!'); }) ]); ``` It can be a string or a Regex: ```js const language = sub(/(en|es|jp)/, ctx => { console.log('Wikipedia <3'); }); ``` ## socket() > *Experimental now, coming stable in version 1.1* ```js const server = require('server'); const { get, socket } = server.router; const { render } = server.reply; server({}, [ get('/', ctx => render('/public/index.html')), // Receive a message from a single socket socket('message', ctx => { // Send the message to every socket io.emit('message', ctx.data); }) ]); ``` # Reply A reply is a method **returned from a middleware** that creates the response. These are the available methods and their parameters for `server.reply`: |reply name |example |final| |-------------------------------------------|----------------------------|-----| |[`cookie(name, value, opts)`](#cookie-) |cookie('name', 'Francisco') |false| |[`download(path[, filename])`](#download-) |download('resume.pdf') |true | |[`file(path)`](#file-) |file('resume.pdf') |true | |[`header(field[, value])`](#header-) |header('ETag': '12345') |false| |[`json([data])`](#json-) |json({ hello: 'world' }) |true | |[`jsonp([data])`](#jsonp-) |jsonp({ hello: 'world' }) |true | |[`redirect([status,] path)`](#redirect-) |redirect(302, '/') |true | |[`render(view[, locals])`](#render-) |render('index.hbs') |true | |[`send([body])`](#send-) |send('Hello there') |true | |[`status(code)`](#status-) |status(200) |mixed| |[`type(type)`](#type-) |type('html') |false| Examples: ```js const { get, post } = require('server/router'); const { render, redirect, file } = require('server/reply'); module.exports = [ get('/', ctx => render('index.hbs')), post('/', processRequest, ctx => redirect('/')) ]; ```
Make sure to **return** the reply that you want to use. It won't work otherwise.
The `ctx` argument is [explained in middleware's Context](/documentation/context). The reply methods can be imported in several ways: ```js // For whenever you have previously defined `server` const { send, json } = server.reply; // For standalone files: const { send, json } = require('server/reply'); ``` There are many more ways of importing the reply methods, but those above are the recommended ones. ### Chainable While most of the replies are final and they should be invoked only once, there are a handful of others that can be chained. These add something to the ongoing response: - [cookie()](#cookie-): add cookie headers - [header()](#header-): add any headers you want - [status()](#status-): set the status of the response - [type()](#type-): adds the header 'Content-Type' You can chain those among themselves and any of those with a final method that sends. If no final method is called in any place the request will be finished with a 404 response. The `status()` reply can be used as final or as chainable if something else is added. ### Return value Both in synchronous mode or asyncrhonous mode you can just return a string to create a response: ```js // Send a string const middle = ctx => 'Hello 世界'; // Test it const res = await run(middle).get('/'); expect(res.body).toBe('Hello 世界'); ``` Returning an array or an object will stringify them as JSON: ```js server(ctx => ['life', 42]); // Note: extra parenthesis needed by the arrow function to return an object server(ctx => ({ life: 42 })); ``` A single number will be interpreted as a status code and the corresponding body for that status will be returned: ```js server(get('/nonexisting', => 404)); ``` You can also throw anything to trigger an error: ```js const middle = ({ req }) => { if (!req.body) { throw new Error('No body provided'); } } const handler = error(ctx => ctx.error.message); // Test it const res = await run(middle, handler).get('/nonexisting'); expect(res.body).toBe('No body provided'); ``` ### Multiple replies Another important thing is that the first reply used is the one that will be used. However, you should try to avoid this and we might make it more strict in the future: ```js // I hope you speak Spanish server([ ctx => 'Hola mundo', ctx => 'Hello world', ctx => 'こんにちは、世界' ]); ``` To avoid this, just specify the url for each request in a [router](/documentation/router): ```js // I hope you speak Spanish server([ get('/es', ctx => 'Hola mundo'), get('/en', ctx => 'Hello world'), get('/jp', ctx => 'こんにちは、世界') ]); ``` Then each of those URLs will use a different language. ## cookie() Send one or multiple cookies to the browser: ```js cookie(name, value, [options]) ``` By default it will _just_ set a cookie and not finish the request, so if you want to also send the body you need to do it explicitly: ```js server( ctx => cookie('foo', 'bar'), // Set a cookie ctx => cookie('xyz', { obj: 'okay' }), // Stringify the object ctx => cookie('abc', 'def', { maxAge: 100000 }), // Pass some options ctx => cookie('fizz', 'buzz').send(), // Set cookie AND finish the request ); ``` ```js const { cookie } = server.reply; const setCookie = ctx => cookie('foo', 'bar').send(); // Test run(setCookie).get('/').then(res => { expect(res.headers['Set-Cookie:']).toMatch(/foo\=bar/); }); ``` ### Cookie Options The options is an optional object with these keys/values: | Key | Default | Type | |-------------|------------------------|-------------------| | `domain` | Current domain | String | | `encode` | `encodeURIComponent` | Function | | `expires` | `undefined` (session) | Date | | `httpOnly` | `false` | Boolean | | `maxAge` | `undefined` (session) | Number | | `path` | `"/"` | String | | `secure` | `false` | Boolean | | `signed` | `false` | Boolean | | `sameSite` | `false` | Boolean or String | See a better explanation of each one of those in [express' documentation](https://expressjs.com/en/4x/api.html#res.cookie). ## download() An async function that takes a local path and an optional filename. It will return the local file with the filename name for the browser to download. ```js server(ctx => download('user-file-5674354.pdf')); server(ctx => download('user-file-5674354.pdf', 'report.pdf')); ``` You can handle errors for this method downstream: ```js server([ ctx => download('user-file-5674354.pdf'), error(ctx => { console.log(ctx.error); }) ]); ``` ## file() Send a file to the browser with the correct mime type: ```js server(ctx => file('user-profile-5674354.png')); ``` It does not accept a name since the user is not prompted for download. It will stream the file, so that no files are read fully into memory. You can handle errors for this method downstream: ```js server([ ctx => file('user-profile-5674354.png'), error(ctx => { console.log(ctx.error); }) ]); ``` ## header() Set a header to be sent with the response. It accepts two strings as key and value or an object to set multiple headers: ```js const mid = ctx => header('Content-Type', 'text/plain'); const mid2 = ctx => header('Content-Length', '123'); // Same as above const mid = ctx => header({ 'Content-Type': 'text/plain', 'Content-Length': '123' }); ``` You can also send multiple headers with the same name by passing an array as the second parameter: ```js const mid = ctx => header({ 'Link': ['Fake Value', 'Another Fake Value'] }); ``` This [can be chained](#chainable) with other methods to e.g. prompt for a download: ```js const mid = async ctx => { const data = await readFileAsync('./hello.pdf', 'utf-8'); return header({ 'Content-Disposition': `filename='welcome.pdf'` }) .type('application/pdf') .send(new Buffer(data)); }; ``` ## json() Sends a JSON response. It accepts a plain object or an array that will be stringified with `JSON.stringify`. Sets the correct `Content-Type` headers as well: ```js const mid = ctx => json({ foo: 'bar' }); // Test it run(mid).get('/').then(res => { expect(res.body).toEqual(`{"foo":"bar"}`); }); ``` ## jsonp() Same as [json()](#json) but wrapped with a callback. [Read more about JSONP](https://en.wikipedia.org/wiki/JSONP): ```js const mid = ctx => jsonp({ foo: 'bar' }); // Test it run(mid).get('/?callback=callback').then(res => { expect(res.body).toMatch('callback({foo:"bar"})'); }); ``` It is useful for loading data Cross-Domain. The query `?callback=foo` **is mandatory** and you should set the callback name there: ```js const mid = ctx => jsonp({ foo: 'bar' }); // Test it run(mid).get('/?callback=foo').then(res => { expect(res.body).toMatch('foo({foo:"bar"})'); }); ``` ## redirect() Redirects to the url specified. It can be either internal (just a path) or an external URL: ```js const mid1 = ctx => redirect('/foo'); const mid2 = ctx => redirect('../user'); const mid3 = ctx => redirect('https://google.com'); const mid4 = ctx => redirect(301, 'https://google.com'); ``` ## render() This is the most complex method and yet the most useful one. It takes a filename and some data and renders it: ```js const mid1 = ctx => render('index.hbs'); const mid2 = ctx => render('index.hbs', { user: 'Francisco' }); ``` The filename is relative to the [views option](/documentation/options/#-views-) (defaults to `'views'`): ```js // Renders PROJECT/somefolder/index.hbs server({ views: 'somefolder' }, ctx => render('index.hbs')); ``` The extension of this filename is optional. It accepts by default `.hbs`, `.pug` and `.html` and can accept more types [installing other engines](/documentation/options/#-engine-): ```js const mid1 = ctx => render('index.pug'); const mid2 = ctx => render('index.hbs'); const mid3 = ctx => render('index.html'); ``` The data will be passed to the template engine. Note that some plugins might pass additional data as well. ## send() Send the data to the front-end. It is the method used by default with [the raw returns](#raw-return): ```js const mid1 = ctx => send('Hello 世界'); const mid2 = ctx => 'Hello 世界'; ``` However it supports many more data types: String, object, Array or Buffer: ```js const mid1 = ctx => send('Hello 世界'); const mid2 = ctx => send('

Hello 世界

'); const mid4 = ctx => send({ foo: 'bar' }); const mid3 = ctx => send(new Buffer('whatever')); ``` It also has the advantage that it can be chained, unlike just returning the string: ```js const mid1 = ctx => status(201).send({ resource: 'foobar' }); const mid2 = ctx => status(404).send('Not found'); const mid3 = ctx => status(500).send({ error: 'our fault' }); ``` ## status() Sets the status of the response. If no reply is done, it will become final and send that response message as the body: ```js const mid1 = ctx => status(404); // The same as: const mid2 = ctx => status(404).send('Not found'); ``` ## type() Set the `Content-Type` header for the response. It can be a explicit MIME type like these: ```js const mid1 = ctx => type('text/html').send('

Hello

'); const mid2 = ctx => type('application/json').send(JSON.stringify({ foo: 'bar' })); const mid3 = ctx => type('image/png').send(...); ``` Or you can also write their more friendly names for an equivalent result: ```js const mid1 = ctx => type('.html'); const mid2 = ctx => type('html'); const mid3 = ctx => type('json'); const mid4 = ctx => type('application/json'); const mid5 = ctx => type('png'); ``` # Errors
If you happen to stumble here, this bit of the documentation is outdated and follows some old code. Please help us improve the project and the docs so we can make it into the official release.
There are many type of errors that can occur with server.js and here we try to explain them and how to fix them. They are divided by category: where/why they are originated. We also overview here how to handle errors. You have to [first define it](#define-an-error), then [throw the error](#throw-the-error) and finally [handle the error](#error-handling). ### Define an error To define an error in your code the best way to do it is to use the package `human-error` (by the author of server), since it's made to combine perfectly with server.js. In the future we might integrate it, but so far they are kept separated. To define an error, create a different file that will contain all or part of your errors, here called `errors.js` for our site `mycat.com`: ```js // errors.js const errors = require('human-error')(); // <-- notice this errors['/mycat/nogithubsecret'] = ` There is no github secret set up. Make sure you have saved it in your '.env', and if you don't have access go see Tom and he'll explain what to do next. https://mycat.com/guide/setup/#github `; module.exports = errors; ``` ### Throw the error Now let's use it, to do so we'll just need to import this file and throw the corresponding error: ```js const server = require('server'); const HumanError = require('./errors'); server(ctx => { if (!ctx.options.githubsecret) { throw new HumanError('/mycat/nogithubsecret'); } }); ``` Try it! Run the code with `node .` and try accessing [http://localhost:3000/](http://localhost:3000). You should see a `server error` on the front-end and the proper description in the back-end. ### Error handling Now this was an error for the developers where we want to be explicit and show the error clearly. For users thought things change a bit and are greatly improved by server's error handling. First let's deal with super type checking: ```js const route = get('/post/:id', ctx => { if (!/^\d+$/.test(ctx.params.id)) { throw new HumanError('/mycat/type/invalid', { base: '/post' }); } }); // Handle a wrong id error and redirect to a 404 const handle = error('/mycat/type/invalid', async ctx => { return redirect(`/${ctx.error.base || ''}?message=notfound`); }); // Handle all type errors in the namespace "mycat" const handleType = error('/mycat/type', () => { return redirect(`/${ctx.error.base || ''}?message=notfound`); }); // Handle all kind of unhandled errors in the namespace "mycat" const handleAll = error('/mycat', () => { return status(500); }); ``` Let's say that someone is trying to access something they don't have access to. Like deleting a comment that is not theirs: ```js // comments.js module.exports = [ ... del('/comment/:id', async ctx => { const comment = await db.comment.findOne({ _id: ctx.params.id }); if (!comment.author.equals(ctx.user._id)) { throw new HumanError('/mycat/auth/unauthorized', { user: ctx.user._id }); } }) ]; ``` Later on you can handle this specific error, we could log these specific kind of errors, etc. ## Native ### /server/native/portused This happens when you try to launch `server` in a port that is already being used by another process. It can be another server process or a totally independent process. To fix it you can do: - Check that there are no other terminals running this process already. - Change the port for the server such as `server({ port: 5000 });`. - Find out what process is already using the port and stop it. In Linux: `fuser -k -n tcp 3000`. Example on when this error is happening: ```js const server = require('server'); // DO NOT DO THIS: server(3000); server(3000); ``` To fix it, invoke it with a different port: ```js const server = require('server'); server(2000); server(3000); ``` ## Options These errors are related to server's options. ### /server/options/portnotanumber ## Core These errors occur when handling a specific part of server.js. ### /server/core/missingmiddleware This will normally happen if you are trying to create a `server` middleware from an `express` middleware but forget to actually pass express' middleware. This error happens when you call `modern()` with an empty or falsy value: ```js const { modern } = server.utils; const middle = modern(); // Error ``` ### /server/core/invalidmiddleware This happens when you try to call `modern()` with an argument that is not an old-style middleware. The first and only argument for `modern()` is a function with `express`' middleware signature. This error should also tell you dynamically which type of argument you passed. ```js const { modern } = server.utils; const middle = modern('hello'); ``` # Plugins
If you happen to stumble here, this bit of the documentation is under active construction and should not be used at all. Please help us improve the project and the docs.
## Create a plugin ### API Here comes the big one, plugins. First, a wish list. I'd like for a plugin to have this API available: ```js module.exports = { // This is working right now (highly unstable) // String name: 'whatever', // Object config: {} // Function, Array init: () => {}, // Function, Array before: () => {}, // Function, Array after: () => {}, // Function, Array final: () => {}, // Not working yet but desirable: // Function (named 'whatever' like the plugin), Object with { name: fn } pairs router: () => {}, // Function (named 'whatever' like the plugin), Object with { name: fn } pairs reply: () => {} }; ``` Now, I am not 100% it makes sense to open `router` and `reply` right now. I think there are some situations where it'd be really useful, like sending a PDF back for example: ```js // Send a pdf from server server(ctx => pdf('./readme.pdf')); ``` But there are many options here and doing it one way might limit some other options. So my first question: #### Simple example: database Why are these useful? Isn't is enough with middleware? Well no, for instance for database connections it is really useful. Let's say we develop a `@server/mongoose` and install it with `npm install @server/mongoose`. Afterwards, just passing the options and we have access to the db through the context: ```js server({ mongoose: 'url for mongodb (or in .env)' }, get('/', ctx => 'Hello world'), get('/sales', hasUser, ctx => ctx.db.sales.find({ user: ctx.user.id })) }); ``` This can be applied to anything that has to be connected or configured once initially and later on can be used in the middleware. #### Advanced example: sass It also opens up to new possibilities, let's see a small example with `sass`. Let's say that we want to make a sass plugin that rebuilds the whole thing on each request for dev and only once on production: ```js module.exports = { name: 'sass', options: { __root: 'source', source: { default: 'style/style.scss', type: String, file: true }, destination: { default: 'public/style.css', type: String } }, init: async ctx => { // If `ctx.options.sass.destination` exists and was not generated by @server/sass // throw an early error and ask for the file to be (re)moved // Remove the `ctx.options.sass.destination` file if (ctx.options.env === 'production') { // Compile everything and store it in `ctx.options.sass.destination` } }, // This will only get called in dev+test, since `style.css` will be found in production before: get('/style.css', async ctx => { // Reply with the whole `ctx.options.sass.source` compiled dynamically }) }; ``` To use it is really simple. First `npm install @server/sass`, then if your options are the default ones you won't even need to write any specific javascript for it. Let's say though that we want to change our source file, which is the *root* option: ```js server({ sass: './front/style.sass' }, ctx => render('index')); ``` That's it, with the flexibility of plugins you wouldn't need any more code to have a sass plugin. This is why I think plugins can be really awesome if they are built and documented properly. **What plugin would you like to see?** ### Options A small exploration about how the options for plugins might look like **for a developer of a plugin**. Now I've written quite a few and have a better idea of the possibilities and limitations of them. I will be using `log` as an example. For the simple way with defaults: ```js plugin.options = { level: { default: 'info' }, reporter: { default: process.stdout }, __root: 'level' }; ``` There are no mandatory fields, however setting a default is strongly recommended. The last root bit would make both of these usages equivalent when using the plugin `log`: ```js server({ log: 'info' }); server({ log: { level: 'info' } }); ``` The `.env` is also quite straightforward in this situation thanks to the `__root` option. Both of these are equivalent as well: ```bash # Single option LOG=info # Multiple options (note: cannot do a function here though!) LOG_LEVEL=info ``` Now you might want all bells and whistles going on. For instance, let's define the type of the parameter. This will add a small validate function internally: ```js log.options = { level: { default: 'info', type: String } }; ``` List of advanced options with their defaults inspired by Mongoose. First, options to use the correct variable: - `default`: the default to set in case it is not set. Leave it unset and it won't have a value if it is not explicitly set. - `env: NAME || true`: defines the name for that variable in the environment variables. If set to false, it will not accept it through the environment. - `arg: NAME || true`: defines the key of the value for options in `server(OPTIONS)`. If set to false it will not accept it from the options object (some arguments that MUST only be accepted through the environment). - `inherit: NAME || false`: passing a name, it inherits the value from a global variable by this order of preference: [validate:] specific environment > global environment > specific argument > global argument > specific default > global default. - `find: FN || false`: a function that receives the options passed on the main function, then all of the environment and finally the default. It returns the wanted value. - `extend: true || false`: extend the default value for the unwritten properties with the default props if they are not set. Value passed: `{ main: 'a', second: 'b' }`, `{ default: { second: 'c', third: 'd' }, extend: true }` => `{ main: 'a', second: 'b', third: 'd' }`. Then you can perform several validations: - `required: FN || false`: make sure the option is set before proceeding. This doesn't make sense when `default` is set. - `type: false`: define the type of the variable. A type or an array of types. Will only check the primitive types `Boolean`, `Number`, `String`, `Array`, `Object`. Can be also an array of types. - `enum: ['a', 'b']`: the variable should be within the list. - `validate: FN || false`: defines a function to validate the value. It will accept first the current value, then all the currently set values and must return `true` for a valid value or `false` otherwise. Note: this is done AFTER any of the other specific checks like type check or the enumerate check. > Note: all of the functions described here can be either synchronous or asynchronous by returning a Promise (or using the `async` keyword). TODO: check the engine for `mongoose` to see if it makes sense to extract the validation part. ### Routes for Plugins I have long been wondering whether the routes and reply should be extensible. There are advantages and disadvantages to this. On one hand, we can stick to more traditional requests workflow: get, post, put, delete and socket seem like a really good scope. But then, if you think about plugins and the possibilities there is so much more we can do and make a nice abstraction layer. For example, let's say you are [making a LINE bot](https://github.com/line/line-bot-sdk-nodejs) and there is a LINE plugin for server. This plugin can behave as normal: ```js server( .. post('/line', ctx => { ctx.line.sendMessage('Hello world'); }) ); ``` This is the traditional way. But if we don't limit ourselves to that, we could be doing it one abstraction level up where the implementation details are invisible: ```js const { line } = server.router; const { message } = server.reply; server( line(ctx => { return message('Hello world'); }) ); ``` One of the big issues here would be namespacing. You might want to have a `line` router but also a `line` reply, and the same with some of the others I can think right now: `sms`, `email`, etc. In the example above, you might be tempted to name your reply `message()`, but so might other plugin. Possible solutions: for the plugins you don't get the router from the main server router, but you get it from the plugin: ```js const line = require('@server/line'); server( line.router(ctx => { return line.reply.message(); }) ); ``` This feels too verbose for the otherwise succint server sintax, but right now seems like the best solution. Another solution would be namespacing the whole server in general. This would also ease one of the pain points I am finding more and more frequently: having to import every route/reply I want to use manually: ```js const server = require('server'); const { router, reply } = server; server( router.get('/', ctx => reply.render(...)), router.post('/', ctx => reply.json(...)) ); ``` While it is a bit more verbose, it cuts down on the import logic (favoring smaller files), makes things more explicit and clear and it's really compatible with this idea of plugins: ```js const server = require('server'); const { router, reply } = server; server( router.get('/', ctx => reply.render(...)), router.post('/', ctx => reply.json(...)), router.line(ctx => reply.line(...)), // OR router.line(ctx => reply.line.message(...)) ); ``` # Testing
If you happen to stumble here, this bit of the documentation is outdated and follows some old code. Please help us improve the project and the docs so we can make it into the official release.
There's a small test suite included, but you probably want to use something more specific to your use-case. Testing that a middleware correctly handles the lack of a user: ```js // auth/errors.js (more info in /documentation/errors/) const error = require('server/error'); error['/app/auth/nouser'] = 'You must be authenticated to do this'; module.exports = error; ``` Our main module: ```js // auth/needuser.js const AuthError = require('./errors'); module.exports = ctx => { if (!ctx.user) { throw new AuthError('/app/auth/nouser', { status: 403, public: true }); } }; ``` Then to test this module: ```js // auth/needuser.test.js const run = require('server/test/run'); const needuser = require('./needuser'); describe('auth/needuser.js', () => { it('returns a server error without a user', async () => { const res = await run(needuser).get('/'); expect(res.status).toBe(403); }); it('works with a mocked user', async () => { const mockuser = ctx => { ctx.user = {}; }; const res = await run(mockuser, needuser).get('/'); expect(res.status).toBe(200); }); }); ``` ## run() This function accepts the same arguments as `server()`, however it will return an API that you can use to test any middleware (and, by extension, any route) that you want. The API that it returns so far is this: ```js const run = require('server/test/run'); const api = run(TOTEST); api.get.then(res => { ... }); api.post.then(res => { ... }); api.put.then(res => { ... }); api.del.then(res => { ... }); ``` ## Disable CSRF For testing POST, PUT and DELETE methods you might want to disable CSRF. To do that, just pass it the appropriate option: ```js run({ security: { csrf: false } }, TOTEST); ``` This API accepts as arguments: ```js api.get(URL, OPTIONS); ``` It is using [`request`](https://github.com/request/request) underneath, so the options are the same as for this module. There are few small differences: - It will generate the port randomly from [1024](https://stackoverflow.com/q/413807/938236) to [49151](https://stackoverflow.com/a/113237/938236). However, there is a chance of collision that grows [faster than expected](https://en.wikipedia.org/wiki/Birthday_problem) as your number of tests grows. There's mitigation code going on to avoid collisions so until the tens of thousands of tests it should be fine. - The URLs will be made local internally to `http://localhost:${port}` unless you fully qualify them (which is not recommended).