{"id":21177422,"url":"https://github.com/betsol/spa-server","last_synced_at":"2025-07-09T22:30:43.271Z","repository":{"id":29159395,"uuid":"32689776","full_name":"betsol/spa-server","owner":"betsol","description":"Extensible Node Web Server to run your SPA's (Single Page Applications)","archived":false,"fork":false,"pushed_at":"2017-11-03T18:57:55.000Z","size":53,"stargazers_count":19,"open_issues_count":6,"forks_count":3,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-06-28T11:21:38.937Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/betsol.png","metadata":{"files":{"readme":"README.md","changelog":"changelog.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-03-22T18:48:44.000Z","updated_at":"2022-01-04T16:37:06.000Z","dependencies_parsed_at":"2022-08-02T15:30:15.595Z","dependency_job_id":null,"html_url":"https://github.com/betsol/spa-server","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/betsol/spa-server","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/betsol%2Fspa-server","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/betsol%2Fspa-server/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/betsol%2Fspa-server/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/betsol%2Fspa-server/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/betsol","download_url":"https://codeload.github.com/betsol/spa-server/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/betsol%2Fspa-server/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264504572,"owners_count":23618824,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-20T17:16:01.454Z","updated_at":"2025-07-09T22:30:43.033Z","avatar_url":"https://github.com/betsol.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Node SPA Server\n\n[![Build Status](https://travis-ci.org/betsol/spa-server.svg?branch=develop)](https://travis-ci.org/betsol/spa-server)\n\u003ca href=\"#with-gulp\" title=\"Ready to be used with Gulp\"\u003e\n    \u003cimg alt=\"Gulp ready!\" src=\"https://img.shields.io/badge/gulp-ready-brightgreen.svg\"\u003e\n\u003c/a\u003e\n\nExtensible Node Web Server based on top of [Connect][lib-connect] and\n[serve-static][lib-serve-static] to help you run your SPA's\n(Single Page Applications).\n\nJust point it to your web root directory and it will serve all your static\ngoodness right away! However, when missing URL is requested it performs\nsmart [fallback lookup](#fallback-lookup) (fully configurable).\n\nAlso you can use your own [custom middleware](#custom-middleware) with it\n(or a third-party).\n\nCan be [used with Gulp](#with-gulp).\n\n\n## Usage\n\n```javascript\n\nvar serverFactory = require('spa-server');\n\nvar server = serverFactory.create({\n  path: './build',\n  port: 80,\n  fallback: fallbackConfig\n});\n\nserver.start();\n\n```\n\nPlease see [configuration section](#configuration) for different fallback\nconfigurations.\n\n\n### With Gulp\n\nThis module can be invoked inside of a Gulp task for convenience.\n\n```javascript\n\nvar serverFactory = require('spa-server');\n\ngulp.task('webserver', function () {\n  var server = serverFactory.create({\n    path: './build',\n    port: 80,\n    fallback: fallbackConfig\n  });\n\n  server.start();\n});\n```\n\nJust add code above to your `gulpfile.js` and run `gulp webserver` after that.\n\nPlease see [configuration section](#configuration) for different fallback\nconfigurations.\n\n\n## Installation\n\nIt's exactly like you've already guessed:\n\n`npm install --save spa-server` or `npm install --save-dev spa-server`.\n\n\n## Configuration\n\n| Option             | Type                               | Default                                     | Description\n|--------------------|------------------------------------|---------------------------------------------|-------------\n| path               | `string`                           | `'.'`                                       | Path to your web root\n| hostname           | `string`                           | `undefined`                                 | Hostname to listen for, *any* when not set\n| port               | `integer`                          | `8888`                                      | Listening port\n| fallback           | `string` or `object` or `function` | `undefined`                                 | Fallback lookup configuration, [see below](#fallback-lookup)\n| serveStaticConfig  | `object`                           | [See the code][serve-static-default-config] | Configuration object for serve-static middleware, see it's [options][lib-serve-static-options]\n| middleware         | `array`                            | `[]`                                        | List of your custom middleware \n\n\n### Fallback Lookup\n\nYou can configure fallback lookup using `fallback` configuration property.\nBy default, lookup functionality is disabled.\n\n\n#### Single URL\n\nIf you will set `fallback` option to a **string** value, e.g.: `/index.html`,\nit will serve the specified URL for all missing (404) requests.\nYou can point server to your root application file that way.\n\n```javascript\n\nvar serverFactory = require('spa-server');\n\nvar server = serverFactory.create({\n  path: './build',\n  port: 80,\n  fallback: '/application.html'\n});\n\nserver.start();\n\n```\n\n\n#### Smart lookup\n\nHowever, it's OK to use single URL fallback, but it could be unfair to some\nstatic resources, cause it will serve `text/html` even if `missing.js`\nfile was requested.\n\nTo counter this, the **smart** fallback lookup was introduced. With it,\nyou can specify fallback URL for each individual mime type. The server\nwill do it's best to determine request's mime type by examining it's headers\nand file extension (if present in URL).\n\n```javascript\n\nvar serverFactory = require('spa-server');\n\nvar server = serverFactory.create({\n  path: './build',\n  port: 80,\n  fallback: {\n    'text/html' : '/application.html',\n    'image/*'   : '/images/default.png',\n    '*'         : '/404.html'\n  }\n});\n\nserver.start();\n\n```\n\nThis configuration will serve `application.html` file for every missing\nHTML request and will serve `default.png` for every missing image.\nThe `404.html` will be served for all other missing requests, e.g. `missing.js`.\n\nPossible mime type specifiers are:\n\n- `'{type}/{subtype}'` — matches specific type and subtype\n- `'{type}/*'` — matches all subtypes of specified type\n- `'*'` — matches everything\n\nFallback filter will process the specified rules trying to match most explicit\nones first and least explicit later. The order doesn't matters, but is recommended\nfor readability.\n\nWe are using [node mime][lib-mime] module internally to map filename extensions\nto mime types. You can also use it to specify explicit mime types,\nfor better stability.\n\n```javascript\n\nvar serverFactory = require('spa-server');\nvar mime = require('mime');\n\nvar server = serverFactory.create({\n  path: './build',\n  port: 80,\n  fallback: {\n    mime.lookup('html') : '/application.html',\n    mime.lookup('js') : '/js/default.js'\n  }\n});\n\nserver.start();\n\n```\n\n\n#### Handler function\n\nAnd for ultimate control of the fallback filter you can pass a **function**\nof your own! It will receive the standard\n[request](https://nodejs.org/api/http.html#http_http_incomingmessage) and\n[response](https://nodejs.org/api/http.html#http_class_http_serverresponse)\nobjects and will need to return fallback URL.\nYou can also return `null` to fallback to default 404 page.\n\n```javascript\n\nvar serverFactory = require('spa-server');\n\nvar matcher = new RegExp('\\\\.html?$');\n\nvar server = serverFactory.create({\n  path: './build',\n  port: 80,\n  fallback: function (request, response) {\n    // For all missing HTML files.\n    if (matcher.test(request.url)) {\n      // Falling back to main application file.\n      return '/application.html';\n    }\n    // Falling back to default server 404 page.\n    return null;\n  }\n});\n\nserver.start();\n\n```\n\n\n### Custom middleware\n\nYou can pass list of your own middleware via `middleware` configuration option,\nall passed middleware will be added to underlying Connect's instance. With this\noption you can take full control of the webserver.\n\nEach element of the `middleware` option should be either a function or an object.\n\n\n#### Middleware as a function\n\nAll `function` elements from the `middleware` list will be added before the\nserve-static and built-in fallback handlers to the stack of Connect's middleware.\n\n```javascript\n\nvar serverFactory = require('spa-server');\nvar favicon = require('serve-favicon');\n\nvar server = serverFactory.create({\n  path: './build',\n  port: 80,\n  middleware: [\n    // Do-nothing middleware.\n    function (request, response, next) {\n      next();\n    },\n    // Adding custom header.\n    function (request, response, next) {\n      response.addHeader('X-My-Custom-Header', 'Content');\n      next();\n    },\n    // Using third-party middleware.\n    favicon(__dirname + '/public/favicon.ico')\n  ]\n});\n\nserver.start();\n\n```\n\n\n#### Middleware as an object\n\nSometimes it's not enough to add middleware just before the built-in handlers.\nThe are situations where you want to add custom middleware before the fallback\nhandler, but after the serve-static middleware. We are providing a very powerful\nmechanism to achieve this.\n\nYou can add an object to the `middleware` list to not only provide the custom function,\nbut to specify where this function should be placed in the Connect's middleware stack.\n\nThe following keys are supported in the object:\n \n- `middleware` — your custom middleware function\n- `before` / `after` — name of the built-in middleware that your function will be added before or after (use only one of them at the same time)\n\nThe following position specifier's values are possible:\n\n- `$start` — meta-value to define start of the stack\n- `serve-static` — middleware to serve static content (will not call next middleware if content is found)\n- `fallback` — middleware with fallback functionality\n- `$end` — meta-value to define end of the stack\n\n```javascript\n\nvar serverFactory = require('spa-server');\nvar favicon = require('serve-favicon');\n\nvar server = serverFactory.create({\n  path: './build',\n  port: 80,\n  middleware: [\n  \n    // Will be added to the top of the stack.\n    {\n      middleware: firstMiddleware,\n      before: '$start'\n    },\n\n    // Without position specifier,\n    // will be added to the top of the stack.\n    {\n      middleware: myMiddleware\n    },\n    \n    // The same as two above, but even shorter.\n    myMiddleware,\n    \n    // Will be added after serve-static.\n    {\n      middleware: afterServeStaticMiddleware,\n      after: 'serve-static'\n    },\n    \n    // Will be added before fallback handler.\n    {\n      middleware: beforeFallbackHandler,\n      before: 'fallback'\n    },\n    \n    // Will be added to the end of the stack.\n    {\n      middleware: finalMiddleware,\n      after: '$end'\n    },\n\n  ]\n});\n\nserver.start();\n\n```\n\n**Notice:** if you place your middleware after the `$end` it still will not\nguarantee that it will be executed. Position specifier only affect the placement\nof your middleware in the Connect's stack.\n\n\n## Changelog\n\nPlease see the [complete changelog][changelog] for list of changes.\n\n\n## Contributors\n\nThis library was made possible by [it's contributors][contributors].\n\n\n## Feedback\n\nIf you have found a bug or have another issue with the library —\nplease [create an issue][new-issue].\n\nIf you have a question regarding the library or it's integration with your project —\nconsider asking a question at [StackOverflow][so-ask] and sending me a\nlink via [E-Mail][email]. I will be glad to help.\n\nHave any ideas or propositions? Feel free to contact me by [E-Mail][email].\n\nCheers!\n\n\n## License\n\nThe MIT License (MIT)\n\nCopyright (c) 2015—2016 Slava Fomin II, BETTER SOLUTIONS\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n\n  [changelog]: changelog.md\n  [contributors]: https://github.com/betsol/spa-server/graphs/contributors\n  [so-ask]: http://stackoverflow.com/questions/ask?tags=node.js,connect\n  [email]: mailto:s.fomin@betsol.ru\n  [new-issue]: https://github.com/betsol/spa-server/issues/new\n  [lib-connect]: https://github.com/senchalabs/connect\n  [lib-serve-static]: https://github.com/expressjs/serve-static\n  [lib-serve-static-options]: https://github.com/expressjs/serve-static#options\n  [lib-mime]: https://github.com/broofa/node-mime\n  [serve-static-default-config]: /lib/server.js#L10-L13\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbetsol%2Fspa-server","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbetsol%2Fspa-server","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbetsol%2Fspa-server/lists"}