{"id":16916273,"url":"https://github.com/joshuawise/vapr","last_synced_at":"2025-04-11T11:31:22.938Z","repository":{"id":57390812,"uuid":"117877204","full_name":"JoshuaWise/vapr","owner":"JoshuaWise","description":"A framework for writing expressive, functional-style apps 🌹","archived":false,"fork":false,"pushed_at":"2021-01-18T00:20:39.000Z","size":283,"stargazers_count":11,"open_issues_count":0,"forks_count":0,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-03-18T15:49:27.512Z","etag":null,"topics":["app","framework","functional-programming","http","server","service"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/JoshuaWise.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"docs/contributing.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-01-17T18:41:03.000Z","updated_at":"2025-02-11T15:55:41.000Z","dependencies_parsed_at":"2022-09-17T03:51:01.839Z","dependency_job_id":null,"html_url":"https://github.com/JoshuaWise/vapr","commit_stats":null,"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JoshuaWise%2Fvapr","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JoshuaWise%2Fvapr/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JoshuaWise%2Fvapr/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JoshuaWise%2Fvapr/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/JoshuaWise","download_url":"https://codeload.github.com/JoshuaWise/vapr/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248383941,"owners_count":21094638,"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":["app","framework","functional-programming","http","server","service"],"created_at":"2024-10-13T19:26:05.703Z","updated_at":"2025-04-11T11:31:22.655Z","avatar_url":"https://github.com/JoshuaWise.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# vapr [![Build Status](https://travis-ci.org/JoshuaWise/vapr.svg?branch=master)](https://travis-ci.org/JoshuaWise/vapr)\nA framework for writing expressive, functional-style apps.\n\nUsing concepts such as immutability, backwards flow control, and observables, Vapr makes complex tasks easy while preventing bugs and remaining unopinionated.\n\nVapr is not a RESTful JSON server, or a template rendering app, or an asset streaming service. Vapr is simply a modern HTTP framework suited for any and all of the above. It comes with powerful high-level features, but leaves application-specific functionality to the middleware/plugins.\n\n## Installation\n\nVapr requires **node v8.9.0** or higher.\n\n```bash\nnpm install --save vapr\n```\n\n## Hello world\n\n```js\nconst app = require('vapr')();\nconst server = require('http').createServer(app);\n\napp.get('/', req =\u003e [['hello world']]);\n\nserver.listen(3000);\n```\n\n## Documentation\n\nIf you're new to Vapr, start with [the guide](#guide) below. All other information (such as the API reference or examples) can be found within [the docs](./docs/index.md).\n\n# Guide\n\n## Routing\n\nRouting is easy.\n\n```js\napp.get('/foo', (req) =\u003e { /* app logic here */ });\napp.post('/bar', (req) =\u003e { /* app logic here */ });\n```\n\nNeed parameters? Also easy.\n\n```js\napp.get('/article/:id', (req) =\u003e {\n  const articleId = req.params.id;\n});\n```\n\nIn the above example, requesting `POST /article/123` would result in a `405 Method Not Allowed`. To have multiple methods on the same path, use `app.route()`.\n\n```js\nconst resource = app.route('/article/:id');\n\nresource.get(getHandler);\nresource.post(postHandler);\n\n// Although uncommon, you can also define a custom 'method not allowed' handler\nresource.noSuchMethod(fn);\n```\n\nIf someone requests a non-existent path, they'll receive a `404 Not Found`, but you can optionally define a custom *not found* handler instead.\n\n```js\napp.notFound(handler);\n```\n\n## Immutable requests\n\nIn many other frameworks, the `req` object is completely mutable. Patterns emerge where a programmer might change the value of a header or query parameter in order to change the behavior of a middleware/plugin down the line. This type of pattern can cause bugs that are very difficult to trace.\n\nWith Vapr, the `req` object is *deeply immutable*, so programmers can safely rely on the values within it, knowing with certainty that they were not modified by some other code.\n\nAs a request is processed, it's common to attach new auxiliary information to it (such as an object that was parsed from a header). To facilitate this, the `req.meta` object is available, and is completely mutable. Any user-defined or plugin-defined information can be placed there.\n\n```js\napp.get('/', (req) =\u003e {\n  const parsedDate = new Date(req.headers.get('date'));\n\n  req.headers.set('date', parsedDate); // Error\n  req.meta.date = parsedDate; // Good\n});\n```\n\n## Expressive responses\n\nIn Vapr, a response is generated by returning or throwing a value. If the value is a number, it will generate an empty response with that status code.\n\n```js\napp.get('/', (req) =\u003e {\n  if (req.headers.has('x-deprecated-header')) throw 400;\n  return 204;\n});\n```\n\nSometimes you may wish to include a custom message or header. To do this, just return an array.\n\n```js\napp.get('/', (req) =\u003e {\n  if (req.headers.has('x-deprecated-header')) throw [400, 'Deprecated Request'];\n  return [204, { 'set-cookie': 'visited=true' }];\n});\n```\n\nResponse bodies must be distinguished from headers and messages, so they get wrapped in another array. Don't worry about efficiency here; small arrays are extremely cheap to create.\n\n```js\napp.get('/', (req) =\u003e {\n  return [200, 'this is a status message', ['this is body text']];\n});\n```\n\nNormally, the status code is required. But when you're just returning a body with a 200 status code, there's a convenient shorthand.\n\n```js\napp.get('/', (req) =\u003e {\n  return [['im a response with a 200 status code']];\n});\n```\n\nThe response body can either be a string, a Buffer, or a [River](#modern-async-tooling) of such. But with the use of plugins, it could be anything.\n\n## Functional middleware\n\nMiddleware (plugins) can be assigned in multiple ways.\n\n```js\n// Insert it before the main route handler\napp.get('/', plugin(), (req) =\u003e { ... });\n\n// Insert as many plugins as you want\napp.get('/', plugin1(), plugin2(), (req) =\u003e { ... });\n\n// Group common plugins together as an array\nconst commonPlugins = [plugin1(), plugin2()];\napp.get('/', commonPlugins, (req) =\u003e { ... });\n\n// Use multiple arrays, and nested arrays\nconst moreCommonPlugins = [commonPlugins, plugin3()];\napp.get('/', moreCommonPlugins, otherPlugins, (req) =\u003e { ... });\n\n// Use the route object itself\nconst route = app.get('/');\nroute.use(commonPlugins, otherPlugins);\nroute.use(specialPlugin());\nroute.use((req) =\u003e { ... });\n```\n\nIn the last example, it's revealed that there's actually no difference between the main route handler and a middleware plugin. A route will simply execute each of the handlers in order, until a response is returned (or thrown), at which point all future handlers are skipped.\n\nAsync handlers are supported automatically. If a handler is an async function, the next handler will not be invoked until the async function finishes.\n\n```js\nroute.use(async (req) =\u003e {\n  req.meta.user = await db.getUser(req.params.id);\n});\n```\n\nSome plugins will need to operate after a response has been generated, but before it's sent to the client. To do this, just return a function. Such a function is called a \"late handler\", and is guaranteed to be called later on, before the response is sent. It will receive the response object as an argument.\n\n```js\nroute.use((req) =\u003e {\n  // this happens before the response is generated\n  return (res) =\u003e {\n    // this happens after the response is generated, before it's sent\n  };\n});\n```\n\nIn many cases, this can be simplified.\n\n```js\nroute.use((req) =\u003e (res) =\u003e {\n  res.headers.set('x-custom-header', 'some value');\n});\n```\n\nRoute handlers behave like a stack. They are called in order until a response is generated. When that happens, control will start flowing in the reverse direction, calling each of the *late handlers* in the opposite order.\n\nLate handlers are capable of mutating the response object, but all properties are guarded by setters/getters, preventing any invalid mutation (such as setting the response code to an object). Additionally, each late handler may return a new response, replacing the existing one for subsequent late handlers.\n\nWhen all handlers and late handlers are finished, the resulting response is finally sent to the client.\n\n## Modern async tooling\n\nVapr abandons the use of low-level asynchronous tools such as callbacks, event emitters, and Node.js streams, instead favoring high-level promises and [observables](https://www.youtube.com/watch?v=-vPFP-2Mkl8).\n\nIn another framework, if you want to write a plugin to parse a request's body as JSON, this would be your code:\n\n```js\nfunction jsonPlugin(req, callback) {\n  const buffers = [];\n  req.on('data', (chunk) =\u003e {\n    buffers.push(chunk);\n  });\n  req.on('end', () =\u003e {\n    let result;\n    try {\n      result = JSON.parse(Buffer.concat(buffers));\n    } catch (err) {\n      callback(err);\n      return;\n    }\n    callback(null, result);\n  });\n  req.on('error', (err) =\u003e {\n    callback(err);\n  });\n  req.on('aborted', () =\u003e {\n    callback(new Error('The request was aborted'));\n  });\n}\n```\n\nBecause of how terrible that is, many frameworks take the opinionated approach of providing JSON support out of the box, making the resulting object available at `req.body`. Unfortunately, this approach has many downsides. For example, imagine you want to check the size of the body before processing it—this would be impossible. Or perhaps your route is for uploading files, so it shouldn't accept JSON. The proper response would be `415 Unsupported Media Type`, but you're only able to send that response after uselessly parsing the JSON body anyways.\n\nVapr is able to remain unopinionated and flexible, while at the same time making it extremely easy for you to impart your own opinions. If you want to replicate the behavior of a more opinioned framework, you can do so with a one-line plugin:\n\n```js\nroute.use(async (req) =\u003e {\n  req.meta.body = await req.read().all().then(Buffer.concat).then(JSON.parse);\n});\n```\n\nThis is all possible because Vapr embraces the use of [observables](https://www.youtube.com/watch?v=-vPFP-2Mkl8). More specifically, Vapr uses a very JavaScripty observable pattern called a [River](https://github.com/JoshuaWise/wise-river). Visit [the repo](https://github.com/JoshuaWise/wise-river) to learn about all the amazing things you can do with Rivers. Or, just forget about it and pretend they're [async iterables](https://github.com/tc39/proposal-async-iteration), because they are:\n\n```js\nroute.use(async (req) =\u003e {\n  const buffers = [];\n  for await (const chunk of req.read()) {\n    buffers.push(chunk);\n  }\n  req.meta.body = JSON.parse(Buffer.concat(buffers));\n});\n```\n\n## Streaming responses\n\nIf you're dealing with large response bodies, you can stream them to reduce the memory footprint of your application and greatly improve stability and latency. Doing this in Vapr is as easy as responding with a [River](https://github.com/JoshuaWise/wise-river) instead of a Buffer.\n\n```js\nconst fs = require('fs');\nconst { River } = require('vapr');\n\n// This function returns a River\nconst streamFile = filename =\u003e River.riverify(fs.createReadStream(filename));\n\napp.get('/:filename', (req) =\u003e {\n  return [[streamFile(req.params.filename)]];\n});\n```\n\nNotice how we didn't need to close the stream, or handle errors. Observables have automatic resource management and error propagation, so we only need to worry about app logic.\n\nAlthough only HTTP/1.1 supports \"chunked\" responses, the above example even works with HTTP/1.0 requests, because Vapr is smart enough to detect the situation and adjust the response accordingly.\n\n## Expected and unexpected errors\n\nIf you can anticipate an error, you can handle it gracefully with ease.\n\n```js\napp.get('/article/:id', (req) =\u003e {\n  if (!isValid(req.params.id)) throw 400;\n});\n```\n\nIf an unexpected error occurs (i.e., an Error object is thrown), it will be converted into a 500 response object. Responses that originate from unexpected errors will have the original error available at `res.error`.\n\n```js\nconst route = app.get('/article/:id');\n\n// The error handler should come first, using a 'late handler'\nroute.use((req) =\u003e (res) =\u003e {\n  if (res.code \u003c 400) return;\n  if (res.error) console.error(res.error);\n  console.log(`A ${res.code} response was generated`);\n});\n\n// This is the main route handler\nroute.use(async (req) =\u003e {\n  const article = await db.findArticle(req.params.id);\n  if (article) return [[removePrivateFields(article)]];\n  return [404, 'Article Not Found'];\n});\n```\n\nThe above example reveals a common pattern found in late handlers. Most late handlers only care about successful responses or error responses, but usually not both. For example, a plugin that sets a cookie might only want to do so for successful responses. Therefore it's very common to use `if (res.code \u003e= 400) return;` within late handlers.\n\n### Unrecoverable errors\n\nSome errors in HTTP are considered unrecoverable. For example, if the response stream errors out after the status code was already sent, the only logical thing to do is to destroy the connection, signaling to the client that the response is incomplete and should be discarded. Most frameworks have no way of gracefully reporting situation like this.\n\nBy default, unrecoverable errors will be emitted as process warnings. If desired, custom logging can be used instead.\n\n```js\nconst app = require('vapr')({ logger: myLoggerFunction });\n```\n\n## Virtual hosting\n\nVapr has the ability to route requests based on the hostname provided in the request. Vapr apps accomplish this by spawning \"child apps\". The parent app will route based on hostname, while the child apps route based on pathname.\n\n```js\nconst parent = require('vapr')();\n\nconst child1 = parent.host('www.mywebsite.com');\nconst child2 = parent.host('dev.mywebsite.com:8080');\nconst child3 = parent.host('*.mywebsite.com:*');\n```\n\nAs seen above, wildcards (`*`) can be used in any subdomain position and/or the port position. Wildcards are only utilized when a request doesn't have an exact match.\n\nEach child app can be used like a regular router.\n\n```js\nchild1.get('/foo', () =\u003e { ... });\nchild2.get('/foo', () =\u003e { ... });\nchild3.get('/foo', () =\u003e { ... });\n```\n\nIf no port is specified in the host string, a default port of `80` is used. You can specify a different default port by passing an option to the parent app constructor.\n\n```js\nconst parent = require('vapr')({ defaultPort: 443 });\n```\n\nIf someone makes a request to an unknown host, they'll receive a `404 Not Found`, but you can optionally define a custom handler instead.\n\n```js\nparent.noHost(handler);\n```\n\n## Correctness and security\n\nVapr takes security very seriously. At the time of this writing, no known HTTP framework for Node.js (besides Vapr) does any validation on the URL of incoming requests. Unfortunately for the users of those frameworks, failing to correctly parse and validate these URLs is a known security vulnerability. Using `url.parse()` or `new URL()` from the builtin [`url`](https://nodejs.org/api/url.html) module is not sufficient. Vapr correctly parses these URLs in accordance with [RFC 7230](https://tools.ietf.org/html/rfc7230) and discards connections that provide invalid URLs.\n\nThere are many other issues with existing frameworks similar to the one described above. Other examples include trimming the whitespace at the end of header values (which Node.js does not do by default for some strange reason, even though the HTTP spec demands it), and ensuring that certain response headers which should be mutually exclusive are indeed treated that way. An exhaustive list of these issues would be too long to cover. Suffice it to say that Vapr is a *true* HTTP framework in the sense that it obeys the HTTP specification very strictly.\n\nAnother feature of correctness is with regards to the string comparison used by the router. Most frameworks treat percent-encoded characters as-is. However, without properly normalizing a url before comparison, strange and difficult-to-trace bugs can occur. Vapr performs proper normalization while routing.\n\n## Efficiency\n\nVapr's router is different from most. Most routers work by linearly scanning a list of regular expressions until a match is found. This is very inefficient, and scales poorly as your application gets bigger. Vapr's router works by traversing a [radix tree](https://en.wikipedia.org/wiki/Radix_tree), which is performed in *constant time*. This doesn't matter for small applications, but it could matter for large ones. Vapr doesn't want to weigh you down, regardless of how big your service is.\n\n![Graph of router throughput as application size increases](https://github.com/JoshuaWise/koa-vapr-comparison/blob/v1.0.0/images/router-throughput.png)\n\nVapr is mostly concerned with being high-level—it doesn't try to be the fastest. Despite this, it still performs well. Below is the result of a simple \"hello world\" benchmark.\n\n|                  | Version       | Requests/s |\n| ---------------- | ------------- | ----------:|\n| http.Server      | 10.6.0        | 62026      |\n| vapr             | 0.5.1         | 45290      |\n| restify          | 7.2.1         | 38499      |\n| koa + koa-router | 2.5.1 + 7.4.0 | 37259      |\n| hapi             | 17.5.1        | 33930      |\n| express          | 4.16.3        | 31189      |\n\n- Machine: MacBook Pro (Mid 2014, 2.8 GHz Intel Core i7, 16 GB 1600 MHz DDR3)\n- Node: v10.6.0\n- Benchmark: [fastify/benchmarks](https://github.com/fastify/benchmarks) (all default settings)\n\n\u003e Don't take \"hello world\" benchmarks too seriously. All of these HTTP frameworks will likely have negligible overhead compared to the work done by a real-world application.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoshuawise%2Fvapr","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjoshuawise%2Fvapr","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoshuawise%2Fvapr/lists"}