22 KiB
Requires node v4.0.0 or higher. Install the previous release for older node support.
local-web-server
A simple, extensible web-server for productive front-end development. Typical use cases:
- Front-end Development
- Static or Single Page App development
- Re-route paths to local or remote resources
- Efficient, predictable, entity-tag-powered conditional request handling (no need to 'Disable Cache' in DevTools, slowing page-load down)
- Bundle with your front-end project
- Very little configuration, just a few options
- Outputs a dynamic statistics view to the terminal
- Configurable log output, compatible with Goaccess, Logstalgia and glTail
- Back-end service mocking
- Prototype a web service, microservice, REST API etc.
- Mocks are defined with config (static), or code (dynamic).
- CORS-friendly, all origins allowed by default.
- Proxy server
- Map local routes to remote servers. Removes CORS pain when consuming remote services.
- HTTPS server
- HTTPS is strictly required by some modern techs (ServiceWorker, Media Capture and Streams etc.)
- File sharing
Synopsis
local-web-server is a simple command-line tool. To use it, from your project directory run ws
.
$ ws --help
local-web-server
A simple web-server for productive front-end development.
Synopsis
$ ws [<server options>]
$ ws --config
$ ws --help
Server
-p, --port number Web server port.
-d, --directory path Root directory, defaults to the current directory.
-f, --log-format string If a format is supplied an access log is written to stdout. If
not, a dynamic statistics view is displayed. Use a preset ('none',
'dev','combined', 'short', 'tiny' or 'logstalgia') or supply a
custom format (e.g. ':method -> :url').
-r, --rewrite expression ... A list of URL rewrite rules. For each rule, separate the 'from'
and 'to' routes with '->'. Whitespace surrounded the routes is
ignored. E.g. '/from -> /to'.
-s, --spa file Path to a Single Page App, e.g. app.html.
-c, --compress Serve gzip-compressed resources, where applicable.
-b, --forbid path ... A list of forbidden routes.
-n, --no-cache Disable etag-based caching -forces loading from disk each request.
--key file SSL key. Supply along with --cert to launch a https server.
--cert file SSL cert. Supply along with --key to launch a https server.
--https Enable HTTPS using a built-in key and cert, registered to the
domain 127.0.0.1.
--verbose Verbose output, useful for debugging.
Misc
-h, --help Print these usage instructions.
--config Print the stored config.
Project home: https://github.com/75lb/local-web-server
Examples
For the examples below, we assume we're in a project directory looking like this:
.
├── css
│ └── style.css
├── index.html
└── package.json
All paths/routes are specified using express syntax. To run the example projects linked below, clone the project, move into the example directory specified, run ws
.
Static site
Fire up your static site on the default port:
$ ws
serving at http://localhost:8000
Single Page Application
You're building a web app with client-side routing, so mark index.html
as the SPA.
$ ws --spa index.html
By default, typical SPA paths (e.g. /user/1
, /login
) would return 404 Not Found
as a file does not exist with that path. By marking index.html
as the SPA you create this rule:
If a static file at the requested path exists (e.g. /css/style.css
) then serve it, if it does not (e.g. /login
) then serve the specified SPA and handle the route client-side.
URL rewriting
Your application requested /css/style.css
but it's stored at /build/css/style.css
. To avoid a 404 you need a rewrite rule:
$ ws --rewrite '/css/style.css -> /build/css/style.css'
Or, more generally (matching any stylesheet under /css
):
$ ws --rewrite '/css/:stylesheet -> /build/css/:stylesheet'
With a deep CSS directory structure it may be easier to mount the entire contents of /build/css
to the /css
path:
$ ws --rewrite '/css/* -> /build/css/$1'
this rewrites /css/a
as /build/css/a
, /css/a/b/c
as /build/css/a/b/c
etc.
Proxied requests
If the to
URL contains a remote host, local-web-server will act as a proxy - fetching and responding with the remote resource.
Mount the npm registry locally:
$ ws --rewrite '/npm/* -> http://registry.npmjs.org/$1'
Map local requests for repo data to the Github API:
$ ws --rewrite '/:user/repos/:name -> https://api.github.com/repos/:user/:name'
Mock Responses
Mocks give you full control over the response headers and body returned to the client. They can be used to return anything from a simple html string to a resourceful REST API. Typically, they're used to mock services but can be used for anything.
In the config, define an array called mocks
. Each mock definition maps a route
to a response
. A simple home page:
{
"mocks": [
{
"route": "/",
"response": {
"body": "<h1>Welcome to the Mock Responses example</h1>"
}
}
]
}
Under the hood, the property values from the response
object are written onto the underlying koa response object. You can set any valid koa response properies, for example type:
{
"mocks": [
{
"route": "/",
"response": {
"type": "text/plain",
"body": "<h1>Welcome to the Mock Responses example</h1>"
}
}
]
}
Conditional Response
To define a conditional response, set a request
object on the mock definition. The request
value acts as a query - the response defined will only be returned if each property of the request
query matches. For example, return an XML response only if the request headers include accept: application/xml
, else return 404 Not Found.
{
"mocks": [
{
"route": "/two",
"request": { "accepts": "xml" },
"response": {
"body": "<result id='2' name='whatever' />"
}
}
]
}
Multiple Potential Responses
To specify multiple potential responses, set an array of mock definitions to the responses
property. The first response with a matching request query will be sent. In this example, the client will get one of two responses depending on the request method:
{
"mocks": [
{
"route": "/three",
"responses": [
{
"request": { "method": "GET" },
"response": {
"body": "<h1>Mock response for 'GET' request on /three</h1>"
}
},
{
"request": { "method": "POST" },
"response": {
"status": 400,
"body": { "message": "That method is not allowed." }
}
}
]
}
]
}
Dynamic Response
The examples above all returned static data. To define a dynamic response, create a mock module. Specify its path in the module
property:
{
"mocks": [
{
"route": "/four",
"module": "/mocks/stream-self.js"
}
]
}
Here's what the stream-self
module looks like. The module should export a mock definition (an object, or array of objects, each with a response
and optional request
). In this example, the module simply streams itself to the response but you could set body
to any valid value.
const fs = require('fs')
module.exports = {
response: {
body: fs.createReadStream(__filename)
}
}
Response function
For more power, define the response as a function. It will receive the koa context as its first argument. Now you have full programmatic control over the response returned.
module.exports = {
response: function (ctx) {
ctx.body = '<h1>I can do anything i want.</h1>'
}
}
If the route contains tokens, their values are passed to the response. For example, with this mock...
{
"mocks": [
{
"route": "/players/:id",
"module": "/mocks/players.js"
}
]
}
...the id
value is passed to the response
function. For example, a path of /players/10?name=Lionel
would pass 10
to the response function. Additional, the value Lionel
would be available on ctx.query.name
:
module.exports = {
response: function (ctx, id) {
ctx.body = `<h1>id: ${id}, name: ${ctx.query.name}</h1>`
}
}
RESTful Resource example
Here's an example of a REST collection (users). We'll create two routes, one for actions on the resource collection, one for individual resource actions.
{
"mocks": [
{ "route": "/users", "module": "/mocks/users.js" },
{ "route": "/users/:id", "module": "/mocks/user.js" }
]
}
Define a module (users.json
) defining seed data:
[
{ "id": 1, "name": "Lloyd", "age": 40, "nationality": "English" },
{ "id": 2, "name": "Mona", "age": 34, "nationality": "Palestinian" },
{ "id": 3, "name": "Francesco", "age": 24, "nationality": "Italian" }
]
The collection module:
const users = require('./users.json')
/* responses for /users */
const mockResponses = [
/* Respond with 400 Bad Request for PUT and DELETE - inappropriate on a collection */
{ request: { method: 'PUT' }, response: { status: 400 } },
{ request: { method: 'DELETE' }, response: { status: 400 } },
{
/* for GET requests return a subset of data, optionally filtered on 'minAge' and 'nationality' */
request: { method: 'GET' },
response: function (ctx) {
ctx.body = users.filter(user => {
const meetsMinAge = (user.age || 1000) >= (Number(ctx.query.minAge) || 0)
const requiredNationality = user.nationality === (ctx.query.nationality || user.nationality)
return meetsMinAge && requiredNationality
})
}
},
{
/* for POST requests, create a new user and return the path to the new resource */
request: { method: 'POST' },
response: function (ctx) {
const newUser = ctx.request.body
users.push(newUser)
newUser.id = users.length
ctx.status = 201
ctx.response.set('Location', `/users/${newUser.id}`)
}
}
]
module.exports = mockResponses
The individual resource module:
const users = require('./users.json')
/* responses for /users/:id */
const mockResponses = [
/* don't support POST here */
{ request: { method: 'POST' }, response: { status: 400 } },
/* for GET requests, return a particular user */
{
request: { method: 'GET' },
response: function (ctx, id) {
ctx.body = users.find(user => user.id === Number(id))
}
},
/* for PUT requests, update the record */
{
request: { method: 'PUT' },
response: function (ctx, id) {
const updatedUser = ctx.request.body
const existingUserIndex = users.findIndex(user => user.id === Number(id))
users.splice(existingUserIndex, 1, updatedUser)
ctx.status = 200
}
},
/* DELETE request: remove the record */
{
request: { method: 'DELETE' },
response: function (ctx, id) {
const existingUserIndex = users.findIndex(user => user.id === Number(id))
users.splice(existingUserIndex, 1)
ctx.status = 200
}
}
]
module.exports = mockResponses
HTTPS Server
Some modern techs (ServiceWorker, any MediaDevices.getUserMedia()
request etc.) must be served from a secure origin (HTTPS). To launch an HTTPS server, supply a --key
and --cert
to local-web-server, for example:
$ ws --key localhost.key --cert localhost.crt
If you don't have a key and certificate it's trivial to create them. You do not need third-party verification (Verisign etc.) for development purposes. To get the green padlock in the browser, the certificate..
- must have a
Common Name
value matching the FQDN of the server - must be verified by a Certificate Authority (but we can overrule this - see below)
First create a certificate:
- Install openssl.
$ brew install openssl
- Generate a RSA private key.
$ openssl genrsa -des3 -passout pass:x -out ws.pass.key 2048
- Create RSA key.
$ openssl rsa -passin pass:x -in ws.pass.key -out ws.key
- Create certificate request. The command below will ask a series of questions about the certificate owner. The most imporant answer to give is for
Common Name
, you can accept the default values for the others. Important: you must input your server's correct FQDN (dev-server.local
,laptop.home
etc.) into theCommon Name
field. The cert is only valid for the domain specified here. You can find out your computers host name by running the commandhostname
. For example, mine ismba3.home
.
$ openssl req -new -key ws.key -out ws.csr
- Generate self-signed certificate.
$ openssl x509 -req -days 365 -in ws.csr -signkey ws.key -out ws.crt
- Clean up files we're finished with
$ rm ws.pass.key ws.csr
- Launch HTTPS server. In iTerm, control-click the first URL (with the hostname matching
Common Name
) to launch your browser.
$ ws --key ws.key --cert ws.crt
serving at https://mba3.home:8010, https://127.0.0.1:8010, https://192.168.1.203:8010
Chrome and Firefox will still complain your certificate has not been verified by a Certificate Authority. Firefox will offer you an Add an exception
option, allowing you to ignore the warning and manually mark the certificate as trusted. In Chrome on Mac, you can manually trust the certificate another way:
- Open Keychain
- Click File -> Import. Select the
.crt
file you created. - In the
Certificates
category, double-click the cert you imported. - In the
trust
section, underneathwhen using this certificate
, selectAlways Trust
.
Now you have a valid, trusted certificate for development.
Built-in certificate
As a quick win, you can run ws
with the https
flag. This will launch an HTTPS server using a built-in certificate registered to the domain 127.0.0.1.
Stored config
Use the same options every time? Persist then to package.json
:
{
"name": "example",
"version": "1.0.0",
"local-web-server": {
"port": 8100,
"forbid": "*.json"
}
}
or .local-web-server.json
{
"port": 8100,
"forbid": "*.json"
}
local-web-server will merge and use all config found, searching from the current directory upward. In the case both package.json
and .local-web-server.json
config is found in the same directory, .local-web-server.json
will take precedence. Options set on the command line take precedence over all.
To inspect stored config, run:
$ ws --config
Logging
By default, local-web-server outputs a simple, dynamic statistics view. To see traditional web server logs, use --log-format
:
$ ws --log-format combined
serving at http://localhost:8000
::1 - - [16/Nov/2015:11:16:52 +0000] "GET / HTTP/1.1" 200 12290 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2562.0 Safari/537.36"
The format value supplied is passed directly to morgan. The exception is --log-format none
which disables all output.
Access Control
By default, access to all files is allowed (including dot files). Use --forbid
to establish a blacklist:
$ ws --forbid '*.json' '*.yml'
serving at http://localhost:8000
Other usage
Debugging
Prints information about loaded middleware, arguments, remote proxy fetches etc.
$ ws --verbose
Compression
Serve gzip-compressed resources, where applicable
$ ws --compress
Disable caching
Disable etag response headers, forcing resources to be served in full every time.
$ ws --no-cache
mime-types
You can set additional mime-type/extension mappings, or override the defaults by setting a mime
value in the stored config. This value is passed directly to mime.define(). Example:
{
"mime": {
"text/plain": [ "php", "pl" ]
}
}
Log Visualisation
Instructions for how to visualise log output using goaccess, logstalgia or gltail here.
Install
Ensure node.js is installed first. Linux/Mac users may need to run the following commands with sudo
.
$ npm install -g local-web-server
This will install the ws
tool globally. To see the available options, run:
$ ws --help
Distribute with your project
The standard convention with client-server applications is to add an npm start
command to launch the server component.
1. Install the server as a dev dependency
$ npm install local-web-server --save-dev
2. Add a start
command to your package.json
:
{
"name": "example",
"version": "1.0.0",
"local-web-server": {
"port": 8100,
"forbid": "*.json"
},
"scripts": {
"start": "ws"
}
}
3. Document how to build and launch your site
$ npm install
$ npm start
serving at http://localhost:8100
API Reference
localWebServer([options]) ⇒ KoaApplication
⏏
Returns a Koa application you can launch or mix into an existing app.
Kind: Exported function
Params
- [options]
object
- options- [.static]
object
- koa-static config- [.root]
string
= "."
- root directory - [.options]
string
- options
- [.root]
- [.serveIndex]
object
- koa-serve-index config- [.path]
string
= "."
- root directory - [.options]
string
- options
- [.path]
- [.forbid]
Array.<string>
- A list of forbidden routes, each route being an express route-path. - [.spa]
string
- specify an SPA file to catch requests for everything but static assets. - [.log]
object
- morgan config- [.format]
string
- log format - [.options]
object
- options
- [.format]
- [.compress]
boolean
- Serve gzip-compressed resources, where applicable - [.mime]
object
- A list of mime-type overrides, passed directly to mime.define() - [.rewrite]
Array.<rewriteRule>
- One or more rewrite rules - [.verbose]
boolean
- Print detailed output, useful for debugging
- [.static]
Example
const localWebServer = require('local-web-server')
localWebServer().listen(8000)
localWebServer~rewriteRule
The from
and to
routes are specified using express route-paths
Kind: inner typedef of localWebServer
Properties
Name | Type | Description |
---|---|---|
from | string |
request route |
to | string |
target route |
Example
{
"rewrite": [
{ "from": "/css/*", "to": "/build/styles/$1" },
{ "from": "/npm/*", "to": "http://registry.npmjs.org/$1" },
{ "from": "/:user/repos/:name", "to": "https://api.github.com/repos/:user/:name" }
]
}
© 2013-16 Lloyd Brookes 75pound@gmail.com. Documented by jsdoc-to-markdown.