{"id":13649261,"url":"https://github.com/curveball/core","last_synced_at":"2025-05-16T04:05:21.527Z","repository":{"id":40649720,"uuid":"138431044","full_name":"curveball/core","owner":"curveball","description":"The Curveball framework is a TypeScript framework for node.js with support for modern HTTP features.","archived":false,"fork":false,"pushed_at":"2024-11-06T22:21:46.000Z","size":1264,"stargazers_count":528,"open_issues_count":4,"forks_count":8,"subscribers_count":18,"default_branch":"main","last_synced_at":"2025-05-07T09:50:23.007Z","etag":null,"topics":["curveball","typescript"],"latest_commit_sha":null,"homepage":"https://curveballjs.org/","language":"TypeScript","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/curveball.png","metadata":{"files":{"readme":"README.md","changelog":"changelog.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":".github/CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2018-06-23T20:22:20.000Z","updated_at":"2025-05-02T04:32:38.000Z","dependencies_parsed_at":"2024-01-14T11:00:31.008Z","dependency_job_id":"44f44ad6-e1ed-4ce9-860d-204cd0fc249c","html_url":"https://github.com/curveball/core","commit_stats":{"total_commits":369,"total_committers":12,"mean_commits":30.75,"dds":"0.11653116531165308","last_synced_commit":"a1ed5c635c2af47e32097e369ac8c61c9089d3b3"},"previous_names":["evert/curveball"],"tags_count":54,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/curveball%2Fcore","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/curveball%2Fcore/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/curveball%2Fcore/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/curveball%2Fcore/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/curveball","download_url":"https://codeload.github.com/curveball/core/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254464895,"owners_count":22075570,"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":["curveball","typescript"],"created_at":"2024-08-02T01:04:53.764Z","updated_at":"2025-05-16T04:05:16.517Z","avatar_url":"https://github.com/curveball.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"Curveball\n=========\n\nCurveball is a framework for building web services in Node.js. It fullfills a\nsimilar role to [Express][1] and it's heavily inspired by [Koa][2].\n\nThis web framework has the following goals:\n\n* A minimal foundation.\n* Completely written in and for [TypeScript][3].\n* Modern Ecmascript features.\n* Async/await-based middleware.\n* Support for Node, [Bun](#Bun%20support), AWS Lambda, Azure functions.\n* Native support for HTTP/2, including easy access to HTTP/2 Push.\n* Native, deeply integrated Websocket.\n* Native support for modern HTTP features, such as [`103 Early Hints`][http-103].\n* The ability to easily do internal sub-requests without having to do a real\n  HTTP request.\n\nIf you used Koa in the past, this is going to look pretty familiar. I'm a big\nfan of Koa myself and would recommend it over this project if you don't need\nany of the things this project offers.\n\nInstallation\n------------\n\n    npm install @curveball/core\n\n\nGetting started\n---------------\n\nCurveball only provides a basic framework. Using it means implementing or\nusing curveball middleware. For example, if you want a router, use or build\na Router middleware.\n\nAll of the following examples are written in typescript, but it is also\npossible to use the framework with plain javascript.\n\n```typescript\nimport { Application, Context } from '@curveball/core';\n\nconst app = new Application();\napp.use((ctx: Context) =\u003e {\n\n  ctx.status = 200;\n  ctx.response.body = 'Hello world!'\n\n});\n\napp.listen(4000);\n```\n\nMiddlewares you might want\n--------------------------\n\n* [Router](https://github.com/curveball/router)\n* [Body Parser](https://github.com/curveball/bodyparser)\n* [Controller][controller]\n* [Access Logs](https://github.com/curveball/accesslog)\n* [Sessions](https://github.com/curveball/session)\n* [Generating application/problem+json responses](https://github.com/curveball/problem)\n* [CORS](https://github.com/curveball/cors)\n* [Hypermedia Links](https://github.com/curveball/links)\n* [Server-rendered React support](https://github.com/curveball/react)\n* [Serving static files](https://github.com/curveball/static)\n* [JSON-Schema validation](https://github.com/curveball/validator)\n\n\nAuthentication\n--------------\n\n* [OAuth2](https://github.com/curveball/oauth2)\n* [OAuth2 add-on to let regular browsers log in](https://github.com/curveball/browser-to-bearer)\n\nYou might like [a12n-server](https://github.com/curveball/a12n-server), a full\nOAuth2 authorization server, written in Curveball and works well with the\nOAuth2 middleware.\n\n\nAWS Lambda support / 'Serverless'\n---------------------------------\n\n* [aws-lambda](https://github.com/curveball/aws-lambda).\n* [Azure functions](https://github.com/curveball/azure-function)\n\n\nBun support\n-----------\n\nTo use Curveball with [Bun](https://bun.sh/), use the kernel package:\n\n```typescript\nimport { Application } from '@curveball/kernel';\n\nconst app = new Application();\n\n// Add all your middlewares here!\napp.use( ctx =\u003e {\n  ctx.response.body = {msg: 'hello world!'};\n});\n\nexport default {\n  port: 3000,\n  fetch: app.fetch.bind(app)\n};\n```\n\nSome more details can be found in this [article](https://evertpot.com/bun-curveball-framework/).\n\nDoing internal subrequests\n--------------------------\n\nMany Node.js HTTP frameworks don't easily allow doing internal sub-requests.\nInstead, they recommend doing a real HTTP request. These requests are more\nexpensive though, as it has to go through the network stack.\n\nCurveball allows you to do an internal request with 'mock' request and\nresponse objects.\n\nSuggested use-cases:\n\n* Running cheaper integration tests.\n* Embedding resources in REST apis.\n\nExample:\n\n```typescript\nimport { Application } from '@curveball/core';\n\nconst app = new Application();\nconst response = await app.subRequest('POST', '/foo/bar', { 'Content-Type': 'text/html' }, '\u003ch1\u003eHi\u003c/h1\u003e');\n```\n\nOnly the first 2 arguments are required. It's also possible to pass a Request object instead.\n\n```typescript\nimport { Application, MemoryRequest } from '@curveball/core';\n\nconst app = new Application();\nconst request = new MemoryRequest('POST', '/foo/bar', { 'Content-Type': 'text/html' }, '\u003ch1\u003eHi\u003c/h1\u003e');\nconst response = await app.subRequest(request);\n```\n\nHTTP/2 push\n-----------\n\nHTTP/2 push can be used to anticipate GET requests client might want to do\nin the near future.\n\nExample use-cases are:\n\n* Sending scripts and stylesheets earlier for HTML-based sites.\n* REST api's sending resources based on relationships clients might want to\n  follow.\n\n```typescript\nimport { Application } from '@curveball/core';\nimport http2 from 'http2';\n\nconst app = new Application();\nconst server = http2.createSecureServer({\n  key: fs.readFileSync('server-key.pem'),\n  cert: fs.readFileSync('server-cert.pem')\n}, app.callback());\nserver.listen(4443);\n\napp.use( ctx =\u003e {\n\n  ctx.response.status = 200;\n  ctx.response.headers.set('Content-Type', 'text/html');\n  ctx.response.body = '';\n\n  await ctx.response.push( pushCtx =\u003e {\n\n    pushCtx.path = '/script.js';\n    return app.handle(pushCtx);\n\n  });\n\n});\n```\n\nHTTP/2 push works by sending HTTP responses to the client, but it also\nincludes HTTP requests. This is because HTTP clients need to know which\nrequest the response belongs to.\n\nThe `push` function simply takes a middleware, similar to `use` on\nApplication.  The callback will only be triggered if the clients supports\npush and wants to receive pushes.\n\nIn the preceding example, we are using `app.handle()` to do a full HTTP\nrequest through all the regular middlewares.\n\nIt's not required to do this. You can also generate responses right in the\ncallback or call an alternative middleware.\n\nLastly, `pushCtx.request.method` will be set to `GET` by default. `GET` is\nalso the only supported method for pushes.\n\n\nSending 1xx Informational responses\n-----------------------------------\n\nCurveball has native support for sending informational responses. Examples are:\n\n* [`100 Continue`][http-100] to let a client know even before the request\n  completed that it makes sense to continue, or that it should break off the\n  request.\n* [`102 Processing`][http-102] to periodically indicate that the server is\n  still working on the response. This might not be very useful anymore.\n* [`103 Early Hints`][http-103] a new standard to let a client or proxy know\n  early in the process that some headers might be coming, allowing clients or\n  proxies to for example pre-fetch certain resources even before the initial\n  request completes.\n\nHere's an example of a middleware using `103 Early Hints`:\n\n```typescript\nimport { Application, Context, Middleware } from '@curveball/core';\n\nconst app = new Curveball();\napp.use(async (ctx: Context, next: Middleware) =\u003e {\n\n  await ctx.response.sendInformational(103, {\n    'Link' : [\n      '\u003c/style.css\u003e rel=\"prefetch\" as=\"style\"',\n      '\u003c/script.js\u003e rel=\"prefetch\" as=\"script\"',\n    ]\n  });\n  await next();\n\n});\n```\n\nWebsocket\n---------\n\nTo get Websocket up and running, just run:\n\n```typescript\napp.listenWs(port);\n```\n\nThis will start a websocket server on the specified port. Any incoming\nWebsocket connections will now *just work*.\n\nIf a Websocket connection was started, the `Context` object will now have a\n`webSocket` property. This property is simply an instance of [Websocket][ws]\nfrom the [ws][ws] NPM package.\n\nExample usage:\n\n```typescript\nimport { UpgradeRequired } from '@curveball/http-errors';\n\napp.use( ctx =\u003e {\n  if (!ctx.webSocket) {\n    throw new UpgradeRequired('This endpoint only supports WebSocket');\n  }\n\n  ctx.webSocket.send('Hello');\n  ctx.webSocket.on('message', (msg) =\u003e {\n    console.log('Received %s', msg);\n  });\n\n});\n```\n\nIf you use typescript, install the `@types/ws` package to get all the correct\ntypings:\n\n    npm i -D @types/ws\n\nThe [Controller][controller] package also has built-in features to make this\neven easier.\n\n\nAPI\n---\n\n### The Application class\n\nThe application is main class for your project. It's mainly responsible for\ncalling middlewares and hooking into the HTTP server.\n\nIt has the following methods\n\n* `use(m: Middleware)` - Add a middleware to your application.\n* `handle(c: Context)` - Take a Context object, and run all middlewares in\n  order on it.\n* `listen(port: number)` - Run a HTTP server on the specified port.\n* `listenWs(port: number)` - Start a websocket server on the specified port.\n* `callback()` - The result of this function can be used as a requestListener\n  for node.js `http`, `https` and `http2` packages.\n* `subRequest(method: string, path:string, headers: object, body: any)` - Run\n  an internal HTTP request and return the result.\n* `subRequest(request: Request)` - Run an internal HTTP request and return the\n  result.\n* `origin` - Sets the 'origin' for the application. This is used to determine\n  absolute URIs. You can set the `origin` directly on the application, but\n  you can also set a `CURVEBALL_ORIGIN` environment variable. If nothing is\n  set this value will default to `http://localhost`.\n\n\n### The Context class\n\nThe Context object has the following properties:\n\n* `request` - An instance of `Request`.\n* `response` - An instance of `Response`.\n* `state` - An object you can use to store request-specific state information.\n  this object can be used to pass information between middlewares. A common\n  example is that an authentication middlware might set 'currently logged in\n  user' information here.\n* `ip()` - Get the `ip` address of the HTTP client that's trying to connect.\n* `path` - The path of the request, for example `/foo.html`.\n* `method` - For example, `POST`.\n* `query` - An object containing the query parametes.\n* `status` - The HTTP status code, for example `200` or `404`.\n* `sendInformational(status, headers?)` - Sends a `100 Continue`,\n  `102 Processing` or `103 Early Hints` - response with optional headers.\n* `push(callback: Middleware)` - Do a HTTP/2 push.\n* `redirect(status, location)` - Send a redirect status code and set a\n  `Location` header.\n* `absoluteUrl` - The absolute URL for the request.\n\n\n### The Request interface\n\nThe Request interface represents the HTTP request. It has the following\nproperties and methods:\n\n* `headers` - An instance of `Headers`.\n* `path` - The path of the request, for example `/foo.html`.\n* `method` - For example, `POST`.\n* `requestTarget` - The full `requestTarget` from the first line of the HTTP\n  request.\n* `body` - This might represent the body, but is initially just empty. It's\n  up to middlewares to do something with raw body and parse it.\n* `rawBody()` - This function uses the [raw-body][5] function to parse the\n  body from the request into a string or Buffer. You can only do this once,\n  so a middleware should use this function to populate `body`.\n* `query` - An object containing the query parametes.\n* `type` - The `Content-Type` without additional parameters.\n* `accepts` - Uses the [accepts][6] package to do content-negotiation.\n* `is(contentType)` - Returns true or false if the `Content-Type` of the\n  request matches the argument. If your `Content-Type` is\n  `application/hal+json` it will return true for `application/hal+json`,\n  `hal+json` and `json`.\n* `origin` - The 'origin' for the request, for example:\n   `http://my-api:8008`.\n* `absoluteUrl` - The absolute URL for the request.\n* `ip()` - Returns the ip address of the client. This may be ipv4 or ipv6.\n  If `CURVEBALL_TRUSTPROXY` is set in the environment and truthy, this will\n  use the information from the `X-Forwarded-For` header (if available).\n\n\n\n### The Response interface\n\nThe Response interface represents a HTTP response. It has the following\nproperties and methods:\n\n* `headers` - An instance of `Headers`.\n* `status` - The HTTP status code, for example `200` or `404`.\n* `body` - The response body. Can be a string, a buffer or an Object. If it's\n  an object, the server will serialize it as JSON.\n* `type` - The `Content-Type` without additional parameters.\n* `sendInformational(status, headers?)` - Sends a `100 Continue`,\n  `102 Processing` or `103 Early Hints` - response with optional headers.\n* `push(callback: Middleware)` - Do a HTTP/2 push.\n* `is(contentType)` - Returns true or false if the `Content-Type` of the\n  response matches the argument. If your `Content-Type` is\n  `application/hal+json` it will return true for `application/hal+json`,\n  `hal+json` and `json`.\n* `redirect(status, location)` - Send a redirect status code and set a\n  `Location` header.\n* `origin` - The 'origin' for the request, for example:\n   `http://my-api:8008`.\n\n\n### The Headers interface\n\nThe Headers interface represents HTTP headers for both the `Request` and\n`Response`.\n\nIt has the following methods:\n\n* `set(name, value)` - Sets a HTTP header.\n* `get(name)` - Returns the value of a HTTP header, or null.\n* `has(name)` - Returns true or false if the header exists.\n* `delete(name)` - Deletes a HTTP header.\n* `append(name, value)` - Adds a HTTP header, but doesn't erase an existing\n  one with the same name.\n* `getAll()` - Returns all HTTP headers as a key-value object.\n\nOther features\n--------------\n\nUse the `checkConditional` function to verify the following headers:\n\n* `If-Match`\n* `If-None-Match`\n* `If-Modified-Since`\n* `If-Unmodified-Since`.\n\nSignature:\n\n```typescript\ncheckConditionial(req: RequestInterface, lastModified: Date | null, etag: string | null): 200 | 304 : 412;\n```\n\nThis function returns `200` if the conditional passed. If it didn't, it will\nreturn either `304` or `412`. The former means you'll want to send a\n`304 Not Modified` back, the latter `412 Precondition Failed`.\n\n`200` does not mean you _have_ to return a `200 OK` status, it's just an easy\nway to indicate that all all conditions have passed.\n\n\n[1]: https://expressjs.com/ \"Express\"\n[2]: https://koajs.com/ \"Koa\"\n[3]: https://www.typescriptlang.org/ \"TypeScript\"\n[5]: https://www.npmjs.com/package/raw-body\n[6]: https://www.npmjs.com/package/accepts\n[http-100]: https://tools.ietf.org/html/rfc7231#section-6.2.1 \"RFC7231: 100 Continue\"\n[http-102]: https://tools.ietf.org/html/rfc2518#section-10.1 \"RFC2518: 102 Processing\"\n[http-103]: https://tools.ietf.org/html/rfc8297 \"RFC8297: 103 Early Hints\"\n[ws]: https://github.com/websockets/ws\n[controller]: https://github.com/curveball/controller\n[bun]: https://github.com/curveball/bun\n[lambda]: https://github.com/curveball/bun\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcurveball%2Fcore","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcurveball%2Fcore","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcurveball%2Fcore/lists"}