{"id":45378176,"url":"https://github.com/jellyfishsolutions/lynx","last_synced_at":"2026-02-21T16:34:51.983Z","repository":{"id":29358779,"uuid":"120930826","full_name":"jellyfishsolutions/lynx","owner":"jellyfishsolutions","description":"Node typescript framework with decorators and async/await supports","archived":false,"fork":false,"pushed_at":"2023-03-04T02:34:09.000Z","size":2302,"stargazers_count":5,"open_issues_count":49,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-11-27T09:09:42.142Z","etag":null,"topics":["nodejs","nodejs-framework","typescript","web"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jellyfishsolutions.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"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}},"created_at":"2018-02-09T16:45:44.000Z","updated_at":"2025-04-06T00:43:20.000Z","dependencies_parsed_at":"2024-06-19T04:11:02.758Z","dependency_job_id":"042c9185-2f1f-4973-94a5-18b12628181b","html_url":"https://github.com/jellyfishsolutions/lynx","commit_stats":{"total_commits":131,"total_committers":3,"mean_commits":"43.666666666666664","dds":0.06870229007633588,"last_synced_commit":"bf58a4904762ca0c5cbae9a9d791c7bdde7caf72"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/jellyfishsolutions/lynx","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jellyfishsolutions%2Flynx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jellyfishsolutions%2Flynx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jellyfishsolutions%2Flynx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jellyfishsolutions%2Flynx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jellyfishsolutions","download_url":"https://codeload.github.com/jellyfishsolutions/lynx/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jellyfishsolutions%2Flynx/sbom","scorecard":{"id":514734,"data":{"date":"2025-08-11","repo":{"name":"github.com/jellyfishsolutions/lynx","commit":"bf58a4904762ca0c5cbae9a9d791c7bdde7caf72"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":1.7,"checks":[{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: Apache License 2.0: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":0,"reason":"91 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-93q8-gq69-wqmw","Warn: Project is vulnerable to: GHSA-w42g-7vfc-xf37","Warn: Project is vulnerable to: GHSA-j5g3-5c8r-7qfx","Warn: Project is vulnerable to: GHSA-fwr7-v2mv-hh25","Warn: Project is vulnerable to: GHSA-4w2v-q235-vp99","Warn: Project is vulnerable to: GHSA-cph5-m8f7-6c5x","Warn: Project is vulnerable to: GHSA-wf5p-g6vw-rhxx","Warn: Project is vulnerable to: GHSA-jr5f-v2jv-69x6","Warn: Project is vulnerable to: GHSA-qwcr-r2fm-qrc7","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-257v-vj4p-3w2h","Warn: Project is vulnerable to: GHSA-pxg6-pf52-xh8x","Warn: Project is vulnerable to: GHSA-897m-rjf5-jp39","Warn: Project is vulnerable to: GHSA-gxpj-cx7g-858c","Warn: Project is vulnerable to: GHSA-w573-4hg7-7wgq","Warn: Project is vulnerable to: GHSA-wm7h-9275-46v2","Warn: Project is vulnerable to: GHSA-4gmj-3p3h-gm8h","Warn: Project is vulnerable to: GHSA-rv95-896h-c2vc","Warn: Project is vulnerable to: GHSA-qw6h-vgh9-j6wx","Warn: Project is vulnerable to: GHSA-74fj-2j2h-c42q","Warn: Project is vulnerable to: GHSA-pw2r-vq6v-hr8c","Warn: Project is vulnerable to: GHSA-jchw-25xp-jwwc","Warn: Project is vulnerable to: GHSA-cxjh-pqwp-8mfp","Warn: Project is vulnerable to: GHSA-ww39-953v-wcq6","Warn: Project is vulnerable to: GHSA-c429-5p7v-vgjp","Warn: Project is vulnerable to: GHSA-43f8-2h32-f4cj","Warn: Project is vulnerable to: GHSA-qqgx-2p2h-9c37","Warn: Project is vulnerable to: GHSA-xvf7-4v9q-58w6","Warn: Project is vulnerable to: GHSA-8cf7-32gw-wr33","Warn: Project is vulnerable to: GHSA-hjrf-2m68-5959","Warn: Project is vulnerable to: GHSA-qwph-4952-7xr6","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3","Warn: Project is vulnerable to: GHSA-vh95-rmgr-6w4m","Warn: Project is vulnerable to: GHSA-xvch-5gv4-984h","Warn: Project is vulnerable to: GHSA-8hfj-j24r-96c4","Warn: Project is vulnerable to: GHSA-wc69-rhjr-hc9g","Warn: Project is vulnerable to: GHSA-44fp-w29j-9vj5","Warn: Project is vulnerable to: GHSA-hwqf-gcqm-7353","Warn: Project is vulnerable to: GHSA-9h6g-pr28-7cqp","Warn: Project is vulnerable to: GHSA-x77j-w7wf-fjmw","Warn: Project is vulnerable to: GHSA-76c9-3jph-rj3q","Warn: Project is vulnerable to: GHSA-hj48-42vr-x3v9","Warn: Project is vulnerable to: GHSA-9wv6-86v2-598j","Warn: Project is vulnerable to: GHSA-rhx6-c78j-4q9w","Warn: Project is vulnerable to: GHSA-x565-32qp-m3vf","Warn: Project is vulnerable to: GHSA-hrpp-h998-j3pp","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-m6fv-jmcg-4jfg","Warn: Project is vulnerable to: GHSA-cm22-4g7w-348p","Warn: Project is vulnerable to: GHSA-3jfq-g458-7qm9","Warn: Project is vulnerable to: GHSA-r628-mhmh-qjhw","Warn: Project is vulnerable to: GHSA-9r2w-394v-53qc","Warn: Project is vulnerable to: GHSA-5955-9wpr-37jh","Warn: Project is vulnerable to: GHSA-qq89-hq3f-393p","Warn: Project is vulnerable to: GHSA-f5x3-32g6-xq36","Warn: Project is vulnerable to: GHSA-fx4w-v43j-vc45","Warn: Project is vulnerable to: GHSA-776f-qx25-q3cc","Warn: Project is vulnerable to: GHSA-c4w7-xm78-47vh","Warn: Project is vulnerable to: GHSA-p9pc-299p-vxgp","Warn: Project is vulnerable to: GHSA-c2jc-4fpr-4vhg","Warn: Project is vulnerable to: GHSA-765h-qjxv-5f44","Warn: Project is vulnerable to: GHSA-f2jv-r9rf-7988","Warn: Project is vulnerable to: GHSA-vfrc-7r7c-w9mx","Warn: Project is vulnerable to: GHSA-7wwv-vh3v-89cq","Warn: Project is vulnerable to: GHSA-29mw-wpgm-hmr9","Warn: Project is vulnerable to: GHSA-35jh-r3h4-6jhm","Warn: Project is vulnerable to: GHSA-6vfc-qv3f-vr6c","Warn: Project is vulnerable to: GHSA-5v2h-r2cx-5xgj","Warn: Project is vulnerable to: GHSA-rrrm-qjm4-v8hf","Warn: Project is vulnerable to: GHSA-4r62-v4vq-hr96","Warn: Project is vulnerable to: GHSA-566m-qj78-rww5","Warn: Project is vulnerable to: GHSA-hwj9-h5mp-3pm3","Warn: Project is vulnerable to: GHSA-7fh5-64p2-3v2j","Warn: Project is vulnerable to: GHSA-rjqq-98f6-6j3r","Warn: Project is vulnerable to: GHSA-mjxr-4v3x-q3m4","Warn: Project is vulnerable to: GHSA-cgfm-xwp7-2cvr","Warn: Project is vulnerable to: GHSA-rm97-x556-q36h","Warn: Project is vulnerable to: GHSA-4rq4-32rv-6wp6","Warn: Project is vulnerable to: GHSA-64g7-mvw6-v9qj","Warn: Project is vulnerable to: GHSA-mxhp-79qh-mcx6","Warn: Project is vulnerable to: GHSA-52f5-9888-hmc6","Warn: Project is vulnerable to: GHSA-cf4h-3jhx-xvhq","Warn: Project is vulnerable to: GHSA-8r6j-v8pm-fqw3","Warn: Project is vulnerable to: MAL-2023-462","Warn: Project is vulnerable to: GHSA-6c8f-qphg-qjgp","Warn: Project is vulnerable to: GHSA-fhjf-83wg-r2j9","Warn: Project is vulnerable to: GHSA-4g88-fppr-53pp","Warn: Project is vulnerable to: GHSA-4jqc-8m5r-9rpr"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-20T01:32:02.383Z","repository_id":29358779,"created_at":"2025-08-20T01:32:02.383Z","updated_at":"2025-08-20T01:32:02.383Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29686795,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-21T15:51:39.154Z","status":"ssl_error","status_checked_at":"2026-02-21T15:49:03.425Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["nodejs","nodejs-framework","typescript","web"],"created_at":"2026-02-21T16:34:51.921Z","updated_at":"2026-02-21T16:34:51.972Z","avatar_url":"https://github.com/jellyfishsolutions.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# lynx\n\nlynx is a NodeJS framework for Web Development, based on decorators and the async/await support.\n\nThe idea is to enforce code maintainability and readability enforcing a precise project structure, **decorators** and completely remove the callback (or promises) nightmare leaning on the new **async/await** supports, available in the new versions of NodeJS.\n\nLynx is influenced by the Java Spring framework, trying to bring a more enterprise-level environment to NodeJS.\n\n## Libraries\n\nLynx is founded by state-of-the-art libraries. It uses:\n\n-   **[ExpressJS](http://expressjs.com/)** for the management of routes;\n-   **[nunjucks](https://mozilla.github.io/nunjucks/)** as template engine;\n-   **[TypeORM](http://typeorm.io/)** as ORM to the database;\n-   **[JWT](https://jwt.io/)** to enable token authentication;\n-   **[multer](https://github.com/expressjs/multer)** to manage file upload;\n-   **[nodemailer](https://nodemailer.com)** to send emails;\n-   **[joi](https://github.com/hapijs/joi)** to validate the requests;\n-   **[jimp](https://github.com/oliver-moran/jimp)** to perform image resizing and other operations.\n\n## Out-Of-The-Box Features\n\nWith Lynx, you will have the following functionality out of the box:\n\n-   user management, and low level function for login, registration, authorization and authentication;\n-   media upload management, file upload and retrieving, on a virtual-folder environment;\n-   multi-language is a first class citizen! You can use new nunjucks filter to enable multi-language on the template engine, but also during fields validation!\n-   datatables, directly integrated on the template engine, with pagination, filtering and ordering (please use the `lynx-datatable` module)\n\n## Installation\n\n```\nnpm install lynx-framework\n```\n\n## Lynx module structure\n\nA Lynx module shall be formed by different folders:\n\n```\n.\n├── controllers\n│   ├── backoffice\n│   │   └── main.controller.ts\n│   └── main.controller.ts\n├── entities\n├── index.ts\n├── libs\n├── locale\n│   ├── en.json\n│   └── it.json\n├── middlewares\n│   └── always.middleware.ts\n├── public\n├── templating\n└── views\n    └── main.njk\n```\n\n-   The `controllers` folder shall contain (with subfolder support) any controllers.\n-   The `entities` folder shall contain any entities, that will be automatically mapped with `TypeORM`.\n-   The `libs` folder shall contain any additional libraries or utility functions, that can be used by controllers and middlewares.\n-   The `local` folder shall contains localization file formatted as key-value JSON file.\n-   The `middlewares` folder shall contain (with subfolder support) any middleware.\n-   The `public` folder shall contain all the public resources, such as images, css and so on.\n-   The `templating` folder shall contain filters and functions to enchant the templating system.\n-   The `view` folder shall contain the nunjucks templates. An `emails` subfolder, containing the email templates, is recommended.\n\nThe `index.ts` should be the entry point of the module, defining a class that implements the `BaseModule` abstract class. For semplicity, also a `SimpleModule` class can be used, in order to define only the folders needed by the module.\n\nThe project structure can be customized with different folder names, simply editing the module class. Otherwise, this structure is strictly recommended.\n\n## Lynx application\n\nA Lynx application is composed by at least one module, and an entry point of the standard node application.\n\nTo start a Lynx application, it is necessary to instantiate a Lynx `App` object. For example, the `index.ts` file can be:\n\n```\nimport { App, ConfigBuilder } from \"lynx-framework/app\";\nimport AppModule from \"./modules/app\";\n\nconst port = Number(process.env.PORT) || 3000;\n\nconst app = new App(new ConfigBuilder(__dirname, false).build(), [new AppModule()]);\napp.startServer(port);\n```\n\nAny Lynx configuration, such as database connection, token secrets, folders and so on, can be customized using the `ConfigBuilder` object.\nAny controllers, middlewares and entities of any modules will be automatically loaded by the Lynx app.\n\n## Controllers\n\nA controller defines a set of endpoints. Any endpoint responds to a specific\npath and HTTP verb, and can generate an HTML or JSON response.\nAny controller shall extends the `BaseController` class, in order to inherit a lot of\nutility methods.\nIt is possible to define only ONE controller for each file, and the class shall be `export default`.\nMoreover, the file should be named as `controllerName.controller.ts`.\n\nThe minimum configuration of a controller is the following:\n\n```\nimport { Route } from \"lynx-framework/decorators\";\nimport BaseController from \"lynx-framework/base.controller\";\n\n@Route(\"/myController/path\")\nexport default class MyController extends BaseController {\n    ...\n}\n```\n\nTo define endpoints, it is necessary to decor a method. There is a decorator for each HTTP verb (GET, POST, etc..). For example:\n\n```\n    ...\n    @GET('/helloWorld')\n    async helloWorld() {\n        return \"Hello, world!\";\n    }\n```\n\nthe method `helloWorld` will be executed for any `GET` request to `/myController/path/helloWorld`.\n\n### Method decorators\n\n#### `GET(path)`, `POST(path)`, `PUT(path)`, `PATCH(path)`, `DELETE(path)`\n\nThese decorators map the method to the chosen HTTP verb with the specified path.\nMoreover, `path` can contains path parameters, for example `/authors/:id/posts`. The path parameters will be injected in the method as arguments:\n\n```\n    @GET('/authors/:id/posts/:secondParameter')\n    async doubleParameters(id:Number, secondParameter:String) {\n        ...\n    }\n```\n\nSince Lynx is based on Express, more information about the url parameters can be found [here](https://expressjs.com/en/guide/routing.html).\n\n**IMPORTANT** these decorators shall be put just before the method definition, and as last decorator used to the method.\n\n### `API()`\n\nThe `API` decorator enforce the serialization of the returned object to JSON. This feature is very useful to build an API.\nIn this case, the returned object will be added inside the following standard return object:\n\n```\n {\n     \"success\": true/false,\n     \"data\": \"returned object serialized\"\n }\n```\n\nIf the method returns a boolean value, the return object will be:\n\n```\n {\n     \"success\": returned value\n }\n```\n\nFor more information about the object serialization, please check the [Entity chapter]().\n\n### `MultipartForm()`\n\nThis decorator simply allows the MultipartForm in post request. It is essential to enable the automatic file upload system.\n\n### `Name(name)`\n\nThis decorator allows to set a name to the route. So, it is possible to recall this route in a very simple way.\nThe route name is used by any `route` functions.\n\n### `Verify(function)`\n\nAdd to the decorated method a verification function that will be executed BEFORE the route.\nThe function must NOT be an async function, and it shell return a boolean value. If true is returned, the method is then executed. This method is fundamental to implement authorization to a single endpoint.\nNOTE: the function shall NOT be a class method, but a proper Typescript function.\nExample:\n\n```\nfunction alwaysDeny(req, res) {\n    return false;\n}\n...\n@Verify(alwaysDeny)\n@GET(\"/unreachable\")\nasync someMethod() {\n    ...\n}\n```\n\n### `AsyncVerify(function)`\n\nAdd to the decorated method a verification function that will be executed BEFORE the route.\nThe function MUST BE an async function, and it shell return a boolean value. If true is returned, the method is then executed. This method is fundamental to implement authorization to a single endpoint.\nNOTE: the function shall NOT be a class method, but a proper Typescript function.\n\n\u003e This method is available from version 0.5.5\n\nExample:\n\n```\nasync function alwaysDeny(req, res) {\n    return false;\n}\n...\n@AsyncVerify(alwaysDeny)\n@GET(\"/unreachable\")\nasync someMethod() {\n    ...\n}\n```\n\n### `IsDisabledOn(function)`\n\nAdd to the decorated method a verification function that will be executed BEFORE the route.\nThe function shall return a boolean value and it is evaluated during the server startup.\nIf the function return true, the decorated method is ignored and is not added to the current controller.\n\n\u003e This method is available from version 1.1.5.\n\nExample:\n\n```\nfunction disableOnProduction() {\n    return isProduction == true;\n}\n...\n@IsDisabledOn(disableOnProduction)\n@GET(\"/test\")\nasync testMethod() {\n    ...\n}\n```\n\n### `Body(name, schema)`\n\nThe `Body` decorator inject the request body as a parameter of the decorated method. The body object\nis automatically wrapped inside a `ValidateObject`, that is verified using a [JOI schema](https://github.com/hapijs/joi).\nExample:\n\n```\nimport { ValidateObject } from \"lynx-framework/validate-object\";\nimport * as Joi from \"joi\";\n\nconst loginSchema = Joi.object().keys({\n    email: Joi.string()\n        .email()\n        .required()\n        .label(\"{{input_email}}\"), //I can use a localized string!\n    password: Joi.string()\n        .required()\n        .min(4)\n        .regex(/^[a-zA-Z0-9]{3,30}$/)\n        .label(\"{{input_password}}\") //I can use a localized string!\n});\n\n...\n\n@Body(\"d\", loginSchema)\n@POST(\"/login\")\nasync performLogin(\n    d: ValidateObject\u003c{ email: string; password: string }\u003e\n) {\n    if (!d.isValid) {\n        //d.errors contains localized errors!\n        return false;\n    }\n    let unwrapped = d.obj; //I can use unwrapped.email and unwrapped.password!\n    ...\n}\n```\n\nStarting from version `0.5.8`, a new builder class can be used to create Joi schemas.\nThe previous example can be simplified as follows:\n\n```\nimport { ValidateObject, SchemaBuilder } from \"lynx-framework/validate-object\";\n\n...\n\nconst loginSchema = new SchemaBuilder()\n    .email(\"email\")\n    .withLabel(\"{{input_email}}\")\n    .password(\"password\")\n    .withLabel(\"{{input_password}}\")\n    .build();\n\n...\n\n@Body(\"d\", loginSchema)\n@POST(\"/login\")\nasync performLogin(\n    d: ValidateObject\u003c{ email: string; password: string }\u003e\n) {\n    if (!d.isValid) {\n        //d.errors contains localized errors!\n        return false;\n    }\n    let unwrapped = d.obj; //I can use unwrapped.email and unwrapped.password!\n    ...\n}\n```\n\n### Advanced\n\n#### Accessing the original `req` and `res`\n\nWhen an endpoint method is called, the last two arguments always are the original `req` and `res` objects.\nThe `req` object has also the `user` and `files` properties (it is a _Lynx Request Object_).\nThe use of `res` object is discouraged, in favor of a standard returned object from the endpoint method.\nExample:\n\n```\n@GET(\"/endpoint/:id\")\nasync myEndpoint(id:Number, req: Request, res: Response) {\n    ...\n}\n```\n\n#### `postConstructor`\n\nIt is possible to override the `postConstructor` methods that will be called after the creation of the controller. This method is `async`, so it is possible to perform asynchronous initialization. Always remember that there will be only ONE instance of any controller in a Lynx application.\n\n## Enhancements to the Nunjucks engine\n\n### `tr` filter\n\nThe `tr` filter automatically localize a string. Usage:\n\n```\n\u003cbutton type=\"submit\" class=\"btn btn-primary px-4\"\u003e{{ \"button_login\" | tr }}\u003c/button\u003e\n```\n\nThe `button_login` shall be a property in the JSON localized file.\n\n### `json` filter\n\nThe `json` filter automatically format an object or variable to JSON.\n\n### `format` filter\n\nThe `format` filter format a number to a string, with a fixed number of decimal digits (default: 2).\nUsage:\n\n```\n\u003cspan class=\"price\"\u003e€ {{ price | format }}\u003c/span\u003e\n\u003cspan class=\"integer_number\"\u003e{{ myNumber | format(0) }}\n```\n\n### `date` filter\n\nThe `date` filter format a date to a string, using the `moment`. The default format will use the\n`lll` format, but it is possible to override this behavior.\nUsage:\n\n```\n\u003cspan class=\"date\"\u003e€ {{ data.createdAt | date }}\u003c/span\u003e\n\u003cspan class=\"my_date_custom\"\u003e{{ data.createdAt | date(\"YYYY-MM-DD\") }}\n```\n\n### `route` global function\n\nThe `route` function compile a route name to an url with the given parameters.\nIf an url is used instead of a route name, the url is still compiled with the given parameters.\nUsage:\n\n```\n\u003ca href=\"{{route('forgot_password')}}\" class=\"btn btn-link px-0\"\u003e...\u003c/a\u003e\n```\n\nTo set the name of a route, use the `Name` decorated to the chosen method.\n\n### `old` global function\n\nThe `old` function is used to retrieve the latest value of a form. It can be used to retrieve the value of an input while performing server side form validation. It is also possible to specify a default value.\nUsage:\n\n```\n\u003cinput type=\"email\" name=\"email\" class=\"form-control\" value=\"{{old('email')}}\"\u003e\n```\n\n### `currentHost` global function\n\nThe `currentHost` function is used to retrieve the current server host. This can be used, with the `route` function, to generate an absolute\nurl (for example, needed to generate an url for an email).\n\n### Add custom filters and functions to the template engine.\n\nIt is possible to add new custom filters and functions using the `TemplateFilter` and `TemplateFunction` decorators.\nIn both cases, it is necessary to create a new class for each filter or function, inside the `templating` folder of the module.\n\nNOTE: filters and functions do not need to be defined as `export default class`es as for controllers or entities. Moreover, only once instance for each class will be created (they are managed as \"singleton\" by Lynx).\n\n#### Custom filter\n\nExample: create a new `currency` filter.\nCreate a new `currency.filter.ts` file inside the `templating` folder as follows:\n\n```\nimport { TemplateFilter } from 'lynx-framework/templating/decorators';\nimport BaseFilter from 'lynx-framework/templating/base.filter';\n\n@TemplateFilter('currency')\nexport class CurrencyFiltering extends BaseFilter {\n    filter(val: any, ...args: any[]): string {\n        if (val == undefined) {\n            return val;\n        }\n        //TODO: convert the `val` variable and return the result\n        return val + '€';\n    }\n}\n\n```\n\n### Custom function\n\nExample: create a new `placeholderUrl` function.\nCreate a new `placeholder-url.function.ts` file inside the `templating` folder as follows:\n\n```\nimport { TemplateFunction } from 'lynx-framework/templating/decorators';\nimport BaseFunction from 'lynx-framework/templating/base.function';\n\n@TemplateFunction('placeholderUrl')\nexport default class PlaceholderUrlFunction extends BaseFunction {\n    execute(...args: any[]) {\n        let ratio = this.safeGet(args, 0);\n        let text = 'Free';\n        if (!ratio) {\n            ratio = '4:3';\n        } else {\n            text = ratio;\n        }\n        let _ww = (ratio + '').split(':')[0];\n        let _hh = (ratio + '').split(':')[1];\n        let h = ((350 / Number(_ww)) * Number(_hh)).toFixed(0);\n        return 'http://via.placeholder.com/350x' + h + '?text=' + text;\n    }\n}\n\n```\n\n## Custom `API` response\n\nStarting from `1.0.0-rc4`, it is possible to customize the standard response of the `API` tagged routes.\n\nTo achieve this feature, it is necessary to implement the `APIResponseWrapper` interface, and set the `apiResponseWrapper` property of your `App` instance.\nBy default, the `DefaultResponseWrapper` implementation is used.\n\n## Lynx Modules\n\nLynx supports custom module to add functionality at the current application. A module act exactly as a legacy Lynx application, with its standard `controllers`, `middlewares`, `entities`, `views`, `locale` and `public` folders.\nModules shall be loaded at startup time, and shall be injected in the Lynx application constructor:\n\n```\nconst app = new App(myConfig, [new DatagridModule(), new AdminUIModule()] as BaseModule[]);\n```\n\nIn this example, the Lynx application is created with the `DatagridModule` and the `AdminUIModule` modules.\n\nModules are the standard to provide additional functionality to the Lynx framework.\n\n## Mail Client\n\nSince day 0, Lynx supports a very simple API to send emails from controllers, with the methods `sendMail` and `sendMailRaw`. Starting from `1.2.0`, this methods are available outside the controller context.\n\nThe `App` class define the `mailClient` property of type `MailClient`. This class contains the methods `sendMail` and `sendMailRaw` to respectivly send emails.\nThe first method uses the `nunjuks` template system to send emails, both for plain text, html text and subject. The latter is a low level version of the API, directly sending the email body and subject. Both APIs support multiple destination addresses.\n\nThe mail client is configured thought the `ConfigBuilder` of the application.\n\nBy default, a standard SMTP sender client is used (using the usual NodeMailer library). It is possible to use a custom sender class (that implements the `MailClient` interface) using the `setMailClientFactoryConstructor` method of the `ConfigBuilder`.\n\n## Interceptors\n\n\u003e This feature is available from version 1.1.21\n\nLynx supports two types of interceptors, in order to manage and edit requests. Currently, _Global Routing Interceptor_ and _Before Perform Response Interceptor_ are supported.\n\n### Global Routing Interceptor\n\nThis interceptor can be mounded as an additional `express Router`, and it is executed before any middleware or routes defined by the lynx modules system.\nSeems the interceptor is mounted as a router, it is possible to define a subpath in which it should be executed.\n\n\u003e Warning: usually, it is possible to use a standard middleware to achieve most of the common jobs. Use this interceptor only if it is necessary to execute this function before any middleware.\n\n### Before Perform Response Interceptor\n\nThis interceptor is executed just before the `performResponse` method of any Lynx `Response` is executed.\nThis interceptor can be useful to override some standard behavior of the framework responses. A typical example is the editing of the final url for a `RedirectResponse`.\n\n### Basic usage\n\nAt Jellyfish Solutions, we use this two interceptors in conjunction, in order to manage url rewrite based on the language.\nIn this particular case, the language of the user is not set in the usual `Accept-Language` header of the request, but it is a portion of the current url.\nThe _Global Routing Interceptor_ is used to remove the language from the request url, and correctly update the usual request language.\nThe _Perform Response Interceptor_ is used to add (if necessary) the correct language to a redirect response, without editing the application code.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjellyfishsolutions%2Flynx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjellyfishsolutions%2Flynx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjellyfishsolutions%2Flynx/lists"}