{"id":28823556,"url":"https://github.com/gcsboss/caf","last_synced_at":"2025-10-27T13:10:35.924Z","repository":{"id":62005894,"uuid":"556014737","full_name":"GCSBOSS/caf","owner":"GCSBOSS","description":"Caf is a light framework for developing RESTful Apps in a quick and convenient manner.","archived":false,"fork":false,"pushed_at":"2022-10-25T11:48:24.000Z","size":24,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-07-13T05:19:02.006Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","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/GCSBOSS.png","metadata":{"files":{"readme":"README.md","changelog":null,"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":"2022-10-22T21:44:02.000Z","updated_at":"2022-10-22T21:48:22.000Z","dependencies_parsed_at":"2022-10-25T02:45:24.927Z","dependency_job_id":null,"html_url":"https://github.com/GCSBOSS/caf","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/GCSBOSS/caf","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GCSBOSS%2Fcaf","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GCSBOSS%2Fcaf/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GCSBOSS%2Fcaf/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GCSBOSS%2Fcaf/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/GCSBOSS","download_url":"https://codeload.github.com/GCSBOSS/caf/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GCSBOSS%2Fcaf/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":281272457,"owners_count":26472708,"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","status":"online","status_checked_at":"2025-10-27T02:00:05.855Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":"2025-06-19T00:10:09.713Z","updated_at":"2025-10-27T13:10:35.918Z","avatar_url":"https://github.com/GCSBOSS.png","language":"TypeScript","readme":"# [Nodecaf](https://gitlab.com/GCSBOSS/nodecaf)\n\n\u003e Docs for version v0.13.x\n\nNodecaf is a light framework for developing RESTful Apps in a quick and convenient manner.\n\n## Highlights\n- URL path pattern routing\n- Each route accepts a single function as an argument ([see why](#simple-routing))\n- Useful [handler arguments](#handlers-args)\n- Optional automatic body parsing for popular formats\n- Support for [Settings files](#settings-file) or objects with straightforward layering\n- [Stdout logging](#logging)\n- Seamless support for [async functions as route handlers](#async-handlers)\n- [Uncaught exceptions](#error-handling) in routes always produce proper REST\n  responses\n- [Assertions for readable RESTful error handling](#rest-assertions)\n- Facility for [exposing global objects](#expose-globals) to all routes (eg.:\n  database connections)\n- [CORS Settings](#cors)\n- Allow calling all endpoints programmatically with complete feature parity (awesome for unit testing)\n- Helper to [handle WebSocket](#handling-websocket) connections.\n- Helpful [command line interface](https://gitlab.com/GCSBOSS/nodecaf-cli)\n\n## Get Started\n\n1. Install the cli utilities: `npm i -P -g nodecaf-cli`.\n2. Create a skelleton project with: `nodecaf init my-project`.\n3. Add your globals in `lib/main.js`\n\n```js\nconst Nodecaf = require('nodecaf');\nconst routes = require('./routes');\n\nmodule.exports = () =\u003e new Nodecaf({\n\n    // Optionally bind to a given port\n    conf: { port: 80 },\n\n    // Load your routes.\n    routes,\n\n    // Perform your server initialization logic.\n    async startup({ conf, log, call }){\n\n    },\n\n    // Perform your server finalization logic.\n    async shutdown({ conf, log, call }){\n\n    }\n});\n```\n\n4. Add your routes in `lib/routes.js`\n\n```js\nconst { post, get, del, head, patch, put, all } = require('nodecaf');\n\nmodule.exports = [\n\n    // Define routes with their handler functions (async or regular no matter).\n    get('/foo/:f/bar/:b', FooBar.read),\n    post('/foo/:f/bar', FooBar.write),\n    // ...\n\n    // This route runs whenever there is no other path match\n    all(Foo.atLast)\n];\n```\n\n5. In your app root directory run with: `nodecaf run .`\n\n## How to Run my App\n\nThere are a few supported ways of running your app dependng on the type of\nenvironment you are targeting.\n\n### Running on development machine\n\n1. You should use the CLI (`npm i -P -g nodecaf-cli`)\n2. Run: `nodecaf run path/to/your/app`\n3. Optionally pass config files with `-c path/to/config`\n4. Optionally enable live reload with `-r`\n\n### Running on Docker for development\n\n- Build the auto-generated `Dockerfile`\n- Bind the port you are going to listen to\n- Create a bind mount to your config files\n- Reference your config files in the `command`\n- Create a bind mount to your app directory targeting `/app` inside the container\n- Run the container\n\nOr use this example compose configuration:\n\n```yml\nmy-app:\n  build: ./my-app\n  command: -c /my-conf.toml\n  ports:\n    - 80:8080\n  volumes:\n    - ./my-conf.toml:/my-conf.toml\n    - ./my-app:/app\n  environment:\n    NODE_ENV: ''\n```\n\n### Running on Docker for production\n- Build and run the auto-generated `Dockerfile` in the same fashion as development\n- You should NOT setup a volume in production so you just use the source code baked in the image\n- Ensure all configuration files referenced in the `command` are accessible inside the container\n- Run the container\n\n### Running as a node module\n\nYour Nodecaf app is exported as a regular node module, so it can run as a dependency in another project\n\n```js\nlet myApp = require('my-app');\n\n(async function(){\n\n    let app = myApp();\n    await app.start();\n\n    let res = await app.trigger('/');\n\n    await app.stop();\n})();\n```\n\n## Reporting Bugs **or Vulnerabilities**\nIf you have found any problems with Nodecaf, please:\n\n1. [Open an issue](https://gitlab.com/GCSBOSS/nodecaf/issues/new).\n2. Describe what happened and how.\n3. Also in the issue text, reference the label `~bug` or `~security`.\n\nWe will make sure to take a look when time allows us.\n\n## Proposing Features\nIf you wish to get that awesome feature or have some advice for us, please:\n1. [Open an issue](https://gitlab.com/GCSBOSS/nodecaf/issues/new).\n2. Describe your ideas.\n3. Also in the issue text, reference the label `~proposal`.\n\n## Contributing\nIf you have spotted any enhancements to be made and is willing to get your hands\ndirty about it, fork us and\n[submit your merge request](https://gitlab.com/GCSBOSS/nodecaf/merge_requests/new)\nso we can collaborate effectively.\n\n- For coding style, we provide an [ESLint](https://eslint.org/) configuration\n  file in the root of the repository.\n- All commits are submit to SAST and Dependency Scanning as well as Code Quality\n  analisys, so expect to be boarded on your MRs.\n\n## Manual\nFormerly based on Express, Nodecaf has a simpler approach to defining routes, offloading much of the complexity to the already existing code partitioning idioms (i.e. functions). Check out how to use all the awesome goodies Nodecaf introduces.\n\n### Handler Args\n\nIn this manual we address as **handler args** the keys in the object passed as\nthe only argument of any route handler function. The code below shows all\nhandler args exposed by Nodecaf:\n\n```js\nfunction({ method, path, res, query, params, body, conf, log, headers, call, websocket }){\n    // Do your stuff.\n}\n```\n\nQuick reference:\n\n- `res`: An object containing the functions to send a response to the client.\n- `path`, `method`, `query`, `params`, `body`, `headers`: Properties of the request.\n  They contain respectively the requested path, HTTP method, query string, the URL parameters, and the request body data.\n- `conf`: This object contains the entire\n  [application configuration data](#settings-file).\n- `log`: A logger instance. Use it to [log events](#logging) of\n  your application.\n- `call`: Calls any user function passing the handler args as the first argument.\n  Signature: `call(userFunc, ...extraArgs)`.\n- Also all keys of the [globally exposed object](#expose-globals) are available\n  as handler args for all routes.\n\n### Settings File\n\nNodecaf allow you to read a configuration file and use it's data in all routes\nand server configuration.\n\nUse this feature to manage:\n- external services data such as database credentials\n- Nodecaf settings such as cors and logging\n- Your own server application settings for your users\n\nSuported config formats: **TOML**, **YAML**, **JSON**, **CSON**\n\n\u003e Check out how to [generate a project with configuration file already plugged in](#init-project)\n\nTo load a config file in your app, use the `-c` flag through the CLI pointing\nto your conf file path: `nodecaf run -c my/conf/path.toml my/app`\n\nYou can use the config data through [it's handler arg](#handler-args) in\nall route handlers as follows:\n\n```js\npost('/foo', function({ conf }){\n    console.log(conf.key); //=\u003e 'value'\n})\n```\n\nConfig data can also be passed as an object to the app constructor in `lib/main.js`:\n\n```js\nmodule.exports = () =\u003e new Nodecaf({ conf: { key: 'value' } });\n```\n\nOr a file path if you want to have a fixed config file for setting defaults or any other reason:\n\n```js\nmodule.exports = () =\u003e new Nodecaf({ conf: __dirname + '/default.toml' });\n```\n\n#### Layered Configs\n\nYou can also use the `app.setup` to add a given configuration\nfile or object on top of the current one as follows:\n\n```js\napp.setup('/path/to/settings.toml');\n\napp.setup('/path/to/settings.yaml');\n\napp.setup({ key: 'value' });\n\napp.setup({ key: 'new-value', foo: 'bar' });\n```\n\nLayering is useful, for example, to keep a **default** settings file in your server\nsource code to be overwritten by your user's.\n\n### Logging\n\nNodecaf logs events to stdout by default where each line of the ouput is a JSON object.\nThe log entries will have some default predefined values like pid, hostname etc...\nIn your route handlers, use the functions available in the `log` object as follows:\n\n```js\nfunction({ log }){\n    log.info('hi');\n    log.warn({ lang: 'fr' }, 'au revoir');\n    log.fatal({ err: new Error() }, 'The error code is %d', 1234);\n}\n```\n\nBelow is described the signature of the available logging methods.\n\n- Method Name: one of the available log levels (`debug`, `info`, `warn`, `error`, `fatal`)\n- First argument (optional): An object whose keys will be injected in the final entry.\n- Second argument: A message to be the main line of the log. May contain printf-like replacements (%d, %s...)\n- Remaning arguments: Will be inserted into the message (printf-like)\n\nNodecaf will automatically log some useful server events as described in the\ntable below:\n\n| Type | Level | Event |\n|-------|-------|-------|\n| error after headers sent | warn | An error happened inside a route after the headers were already sent |\n| route | error | An error happened inside a route and was not caught |\n| crash | fatal | An error happened that crashed the server process |\n| request | debug | A request has arrived |\n| response | debug | A response has been sent |\n| app | debug | The application is starting up |\n| app | info | The application has started |\n| app | info | The application has stopped |\n| app | info | The application configuration has been reloaded |\n| event | warn | Called `res.end()` after response was already finished |\n\nAdditionally, you can filter log entries by level and type with the following\nsettings:\n\n```toml\n[log]\nlevel = 'warn' # Only produce log entries with level 'warn' or higher ('error' \u0026 'fatal')\ntype = 'my-type' # Only produce log entries with type matching exactly 'my-type'\n```\nYou can disable logging entirely for a given app by setting it to `false` in the config\n\n```toml\nlog = false\n```\n\n### Async Handlers\n\nNodecaf accepts async functions as well as regular functions as route handlers.\nAll rejections/error within your async handler will be gracefully handled.\nYou will be able to avoid callback hell without creating bogus adapters for your promises.\n\n```js\nget('/my/thing', function({ res }){\n    res.end('My regular function works!');\n})\n\nget('/my/other/thing', async function({ res }){\n    await myAsyncThing();\n    res.end('My async function works too!');s\n})\n```\n\n### Error Handling\n\nIn Nodecaf, any uncaught synchronous error happening inside route handler will be\nautomatically converted into a harmless RESTful 500.\n\n```js\npost('/my/thing', function(){\n    throw new Error('Should respond with a 500');\n})\n```\n\nTo support the callback error pattern, use the `res.error()` function arg.\n\n```js\nconst fs = require('fs');\n\npost('/my/thing', function({ res }){\n    fs.readFile('./my/file', 'utf8', function(err, contents){\n        if(err)\n            return res.error(err);\n        res.end(contents);\n    });\n})\n```\n\nTo use other HTTP status codes you can send an integer in the first parameter of\n`res.error()`.\n\n```js\npost('/my/thing', function({ error }){\n    try{\n        doThing();\n    }\n    catch(e){\n        error(404, 'Optional message for the response');\n    }\n})\n```\n\n### REST Assertions\n\nNodecaf provides you with an assertion module containing functions to generate\nthe most common REST outputs based on some condition. Check an example to\ntrigger a 404 in case a database record doesn't exist.\n\n```js\nget('/my/thing/:id', function({ params, db, res }){\n    let thing = await db.getById(params.id);\n    res.notFound(!thing, 'thing not found');\n\n    doStuff();\n})\n```\n\nIf the record is not found, the `res.notfound()` call will stop the route execution right\naway and generate a [RESTful `NotFound` error](#error-handling).\n\nAlong with `notFound`, the following assertions with similar behavior are provided:\n\n| Method | Status Code |\n|--------|-------------|\n| `badRequest`   | 400 |\n| `unauthorized` | 401 |\n| `forbidden`    | 403 |\n| `notFound`     | 404 |\n| `conflict`     | 409 |\n| `gone`         | 410 |\n| `badType`      | 415 |\n\n### Expose Globals\n\nNodecaf makes it simple to share global objects (eg.: database connections,\ninstanced libraries) across all route handlers. In your `lib/main.js` you can\nexpose an object of which all keys will become handler args.\n\n```js\nmodule.exports = () =\u003e new Nodecaf({\n    startup({ global }){\n        global.db = myDbConnection;\n        global.libX = new LibXInstance();\n    }\n});\n```\n\nThen in all routes you can:\n\n```js\nget('/my/thing', function({ db, libX }){\n    // use your global stuff\n})\n```\n\n### CORS\n\nNodecaf provides a setting to enable permissive CORS on all routes. Defaults to\ndisabled. In your conf file:\n\n```toml\ncors = true\ncors = 'my://origin'\ncors = [ 'my://origin1', 'my://origin2' ]\n```\n\nSetup the cors according to the [popular CORS Express middleware](https://github.com/expressjs/cors#configuration-options).\n\n### Handling Websocket\n\nUse the `websocket` handler argument to expect a Websocket upgrade.\n\n```js\nget('/my/ws/endpoint', async ({ websocket }) =\u003e {\n\n    // Wait till ws connection is open\n    const ws = await websocket();\n\n    ws.on('message', m =\u003e {\n        ws.send('Hello World!');\n        ws.close();\n    });\n})\n```\n\n### Other Settings\n\n| Property | Type | Description | Default |\n|----------|------|-------------|---------|\n| `app.conf.delay` | Integer | Milliseconds to wait before actually starting the app | `0` |\n| `app.conf.port` | Integer | Port for the web server to listen (also exposed as user conf) | `80` or `443` |\n| `app.conf.cookie.secret` | String | A secure random string to be used for signing cookies | none |\n| `opts.name` | String | Manually set application name used in various places | `package.json`s |\n| `opts.version` | String | Manually set application version | `package.json`s |\n| `opts.shouldParseBody` | Boolean | Wether supported request body types should be parsed | `true` |\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgcsboss%2Fcaf","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgcsboss%2Fcaf","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgcsboss%2Fcaf/lists"}