{"id":22486598,"url":"https://github.com/usehenri/henri","last_synced_at":"2025-09-22T23:00:17.380Z","repository":{"id":3456371,"uuid":"49589597","full_name":"usehenri/henri","owner":"usehenri","description":"The versatile Javascript framework","archived":false,"fork":false,"pushed_at":"2023-01-06T01:46:12.000Z","size":10975,"stargazers_count":51,"open_issues_count":156,"forks_count":6,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-09-08T21:02:46.236Z","etag":null,"topics":["disk","framework","graphql","hacktoberfest","henri","mongoose","nextjs","nodejs","react","react-server-render","server-side-rendering","vue"],"latest_commit_sha":null,"homepage":"https://usehenri.io","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/usehenri.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-01-13T17:23:59.000Z","updated_at":"2025-08-27T12:08:33.000Z","dependencies_parsed_at":"2023-01-13T13:00:15.265Z","dependency_job_id":null,"html_url":"https://github.com/usehenri/henri","commit_stats":null,"previous_names":["simplehub/henri"],"tags_count":89,"template":false,"template_full_name":null,"purl":"pkg:github/usehenri/henri","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/usehenri%2Fhenri","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/usehenri%2Fhenri/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/usehenri%2Fhenri/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/usehenri%2Fhenri/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/usehenri","download_url":"https://codeload.github.com/usehenri/henri/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/usehenri%2Fhenri/sbom","scorecard":{"id":912378,"data":{"date":"2025-08-11","repo":{"name":"github.com/usehenri/henri","commit":"b67a98ff1f939d18ea2fa7a28e25614af639e8c4"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":1.6,"checks":[{"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":"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":"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":"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":"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":"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":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: 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":"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":"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":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: containerImage not pinned by hash: docker/Dockerfile:1: pin your Docker image by updating mhart/alpine-node to mhart/alpine-node@sha256:c9014e9e5b33f29d47c867ea548edc0235ba71677f40456409a44c278d8a8e01","Warn: containerImage not pinned by hash: packages/demo/Dockerfile:1: pin your Docker image by updating mhart/alpine-node to mhart/alpine-node@sha256:c9014e9e5b33f29d47c867ea548edc0235ba71677f40456409a44c278d8a8e01","Info:   0 out of   2 containerImage dependencies pinned"],"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":"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":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 1 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":0,"reason":"184 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-67hx-6x53-jw92","Warn: Project is vulnerable to: GHSA-93q8-gq69-wqmw","Warn: Project is vulnerable to: GHSA-j5g3-5c8r-7qfx","Warn: Project is vulnerable to: GHSA-5wg4-74h6-q47v","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-x9w5-v3q2-3rhw","Warn: Project is vulnerable to: GHSA-w8qv-6jwh-64r5","Warn: Project is vulnerable to: GHSA-pxg6-pf52-xh8x","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","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-r9p9-mrjm-926w","Warn: Project is vulnerable to: GHSA-434g-2637-qmqr","Warn: Project is vulnerable to: GHSA-49q7-c7j4-3p7m","Warn: Project is vulnerable to: GHSA-977x-g7h5-7qgw","Warn: Project is vulnerable to: GHSA-f7q4-pwc6-w24p","Warn: Project is vulnerable to: GHSA-fc9h-whq2-v747","Warn: Project is vulnerable to: GHSA-vjh7-7g9h-fjfh","Warn: Project is vulnerable to: GHSA-j4f2-536g-r55m","Warn: Project is vulnerable to: GHSA-r7qp-cfhv-p84w","Warn: Project is vulnerable to: GHSA-6h5x-7c5m-7cr7","Warn: Project is vulnerable to: GHSA-rv95-896h-c2vc","Warn: Project is vulnerable to: GHSA-qw6h-vgh9-j6wx","Warn: Project is vulnerable to: GHSA-fjxv-7rqg-78g4","Warn: Project is vulnerable to: GHSA-ww39-953v-wcq6","Warn: Project is vulnerable to: GHSA-pfrx-2q88-qq97","Warn: Project is vulnerable to: GHSA-765h-qjxv-5f44","Warn: Project is vulnerable to: GHSA-f2jv-r9rf-7988","Warn: Project is vulnerable to: GHSA-c429-5p7v-vgjp","Warn: Project is vulnerable to: GHSA-43f8-2h32-f4cj","Warn: Project is vulnerable to: GHSA-rc47-6667-2j5j","Warn: Project is vulnerable to: GHSA-qqgx-2p2h-9c37","Warn: Project is vulnerable to: GHSA-896r-f27r-55mw","Warn: Project is vulnerable to: GHSA-9c47-m6qq-7p4h","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-76p3-8jx3-jpfq","Warn: Project is vulnerable to: GHSA-3rfm-jhwj-7488","Warn: Project is vulnerable to: GHSA-hhq3-ff78-jv3g","Warn: Project is vulnerable to: GHSA-p6mc-m468-83gw","Warn: Project is vulnerable to: GHSA-29mw-wpgm-hmr9","Warn: Project is vulnerable to: GHSA-35jh-r3h4-6jhm","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3","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-r683-j2x4-v87g","Warn: Project is vulnerable to: GHSA-w7rc-rwvf-8q5r","Warn: Project is vulnerable to: GHSA-5fw9-fq32-wv5p","Warn: Project is vulnerable to: GHSA-48ww-j4fc-435p","Warn: Project is vulnerable to: GHSA-hwqf-gcqm-7353","Warn: Project is vulnerable to: GHSA-9h6g-pr28-7cqp","Warn: Project is vulnerable to: GHSA-px4h-xg32-q955","Warn: Project is vulnerable to: GHSA-cwx2-736x-mf6w","Warn: Project is vulnerable to: GHSA-v39p-96qg-c8rf","Warn: Project is vulnerable to: GHSA-8v63-cqqc-6r2c","Warn: Project is vulnerable to: GHSA-76c9-3jph-rj3q","Warn: Project is vulnerable to: GHSA-6fx8-h7jm-663j","Warn: Project is vulnerable to: GHSA-v923-w3x8-wh69","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-h7cp-r72f-jxh6","Warn: Project is vulnerable to: GHSA-v62p-rq8g-8h59","Warn: Project is vulnerable to: GHSA-hrpp-h998-j3pp","Warn: Project is vulnerable to: GHSA-5q6m-3h65-w53x","Warn: Project is vulnerable to: GHSA-p8p7-x288-28g6","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-g4rg-993r-mgx7","Warn: Project is vulnerable to: GHSA-fxwf-4rqh-v8g3","Warn: Project is vulnerable to: GHSA-25hc-qcg6-38wj","Warn: Project is vulnerable to: GHSA-xfhh-g9f5-x4m4","Warn: Project is vulnerable to: GHSA-qm95-pgcg-qqfq","Warn: Project is vulnerable to: GHSA-cqmj-92xf-r6r9","Warn: Project is vulnerable to: GHSA-vx3p-948g-6vhq","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-4wf5-vphf-c2xc","Warn: Project is vulnerable to: GHSA-52f5-9888-hmc6","Warn: Project is vulnerable to: GHSA-jgrx-mgxx-jf9v","Warn: Project is vulnerable to: GHSA-72xf-g2v4-qvf3","Warn: Project is vulnerable to: GHSA-9m6j-fcg5-2442","Warn: Project is vulnerable to: GHSA-hh27-ffr2-f2jc","Warn: Project is vulnerable to: GHSA-rqff-837h-mm52","Warn: Project is vulnerable to: GHSA-8v38-pw62-9cw2","Warn: Project is vulnerable to: GHSA-hgjh-723h-mx2j","Warn: Project is vulnerable to: GHSA-jf5r-8hm2-f872","Warn: Project is vulnerable to: GHSA-qgmg-gppg-76g5","Warn: Project is vulnerable to: GHSA-xx4c-jj58-r7x6","Warn: Project is vulnerable to: GHSA-j8xg-fqg3-53r7","Warn: Project is vulnerable to: GHSA-6fc8-4gx4-v693","Warn: Project is vulnerable to: GHSA-3h5v-q93c-6h6q","Warn: Project is vulnerable to: GHSA-72mh-269x-7mh5","Warn: Project is vulnerable to: GHSA-h4j5-c7cj-74xg","Warn: Project is vulnerable to: GHSA-c4w7-xm78-47vh","Warn: Project is vulnerable to: GHSA-vhhw-xjvf-wprr","Warn: Project is vulnerable to: GHSA-v88g-cgmw-v5xw","Warn: Project is vulnerable to: GHSA-7gc6-qh9x-w6h8","Warn: Project is vulnerable to: GHSA-4gmj-3p3h-gm8h","Warn: Project is vulnerable to: GHSA-662x-fhqg-9p8v","Warn: Project is vulnerable to: GHSA-394c-5j6w-4xmx","Warn: Project is vulnerable to: GHSA-78cj-fxph-m83p","Warn: Project is vulnerable to: GHSA-fhg7-m89q-25r3","Warn: Project is vulnerable to: GHSA-g3ch-rx76-35fx","Warn: Project is vulnerable to: GHSA-pp7h-53gx-mx7r","Warn: Project is vulnerable to: GHSA-vxvm-qww3-2fh7","Warn: Project is vulnerable to: GHSA-f825-f98c-gj3g","Warn: Project is vulnerable to: GHSA-h8hf-x3f4-xwgp","Warn: Project is vulnerable to: GHSA-9m93-w8w6-76hh","Warn: Project is vulnerable to: GHSA-m7xq-9374-9rvx","Warn: Project is vulnerable to: GHSA-vg7j-7cwx-8wgw","Warn: Project is vulnerable to: GHSA-p92x-r36w-9395","Warn: Project is vulnerable to: GHSA-45q2-34rf-mr94","Warn: Project is vulnerable to: GHSA-qpfw-4m9x-rxx8","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-4gxf-g5gf-22h4","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-56x4-j7p9-fcf9","Warn: Project is vulnerable to: GHSA-v78c-4p63-2j6c","Warn: Project is vulnerable to: GHSA-wrh9-cjv3-2hpw","Warn: Project is vulnerable to: GHSA-8c25-f3mj-v6h8","Warn: Project is vulnerable to: GHSA-vqfx-gj96-3w95","Warn: Project is vulnerable to: GHSA-f598-mfpv-gmfx","Warn: Project is vulnerable to: GHSA-cf4h-3jhx-xvhq","Warn: Project is vulnerable to: GHSA-776f-qx25-q3cc","Warn: Project is vulnerable to: GHSA-h6q6-9hqw-rwfv","Warn: Project is vulnerable to: GHSA-5fg8-2547-mr8q","Warn: Project is vulnerable to: GHSA-crh6-fp67-6883","Warn: Project is vulnerable to: GHSA-mqr2-w7wj-jjgr","Warn: Project is vulnerable to: GHSA-49j4-86m8-q2jw","Warn: Project is vulnerable to: GHSA-fpw7-j2hg-69v5","Warn: Project is vulnerable to: GHSA-4rch-2fh8-94vw","Warn: Project is vulnerable to: GHSA-pmh2-wpjm-fj45","Warn: Project is vulnerable to: GHSA-257v-vj4p-3w2h","Warn: Project is vulnerable to: GHSA-7r28-3m3f-r2pr","Warn: Project is vulnerable to: GHSA-r8j5-h5cx-65gg","Warn: Project is vulnerable to: GHSA-x56p-c8cg-q435","Warn: Project is vulnerable to: GHSA-vxf5-wxwp-m7g9","Warn: Project is vulnerable to: GHSA-25mp-g6fv-mqxx","Warn: Project is vulnerable to: GHSA-c59h-r6p8-q9wc","Warn: Project is vulnerable to: GHSA-qpjv-v59x-3qc4","Warn: Project is vulnerable to: GHSA-rp65-9cf3-cjxr","Warn: Project is vulnerable to: GHSA-566m-qj78-rww5","Warn: Project is vulnerable to: GHSA-7fh5-64p2-3v2j","Warn: Project is vulnerable to: GHSA-hwj9-h5mp-3pm3","Warn: Project is vulnerable to: GHSA-4943-9vgg-gr5r","Warn: Project is vulnerable to: GHSA-gcx4-mw62-g8wm","Warn: Project is vulnerable to: GHSA-h5c3-5r3r-rr8q","Warn: Project is vulnerable to: GHSA-rmvr-2pp2-xj38","Warn: Project is vulnerable to: GHSA-xx4v-prfh-6cgc","Warn: Project is vulnerable to: GHSA-h452-7996-h45h","Warn: Project is vulnerable to: GHSA-ff7x-qrg7-qggm","Warn: Project is vulnerable to: GHSA-78xj-cgh5-2h22","Warn: Project is vulnerable to: GHSA-2p57-rm9w-gvfp","Warn: Project is vulnerable to: GHSA-3j8f-xvm3-ffx4","Warn: Project is vulnerable to: GHSA-4p35-cfcx-8653","Warn: Project is vulnerable to: GHSA-7f3x-x4pr-wqhj","Warn: Project is vulnerable to: GHSA-jpp7-7chh-cf67","Warn: Project is vulnerable to: GHSA-q6wq-5p59-983w","Warn: Project is vulnerable to: GHSA-j9fq-vwqv-2fm2","Warn: Project is vulnerable to: GHSA-pqw5-jmp5-px4v","Warn: Project is vulnerable to: GHSA-44c6-4v22-4mhx","Warn: Project is vulnerable to: GHSA-4x5v-gmq8-25ch","Warn: Project is vulnerable to: GHSA-7p7h-4mm5-852v","Warn: Project is vulnerable to: GHSA-38fc-wpqx-33j7"],"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-24T19:42:32.224Z","repository_id":3456371,"created_at":"2025-08-24T19:42:32.224Z","updated_at":"2025-08-24T19:42:32.224Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":276487478,"owners_count":25651133,"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-09-22T02:00:08.972Z","response_time":79,"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":["disk","framework","graphql","hacktoberfest","henri","mongoose","nextjs","nodejs","react","react-server-render","server-side-rendering","vue"],"created_at":"2024-12-06T17:14:58.178Z","updated_at":"2025-09-22T23:00:17.277Z","avatar_url":"https://github.com/usehenri.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"\u003ca href=\"http://usehenri.io\" target=\"_blank\"\u003e\n  \u003cp align=\"center\"\u003e\n    \u003cimg width=\"100\" alt=\"\" src=\"https://raw.githubusercontent.com/simplehub/henri/master/henri.png\"\u003e\n  \u003c/p\u003e\n\u003c/a\u003e\n\n# henri - the versatile javascript framework\n\n[![npm version](https://img.shields.io/npm/v/henri.svg?style=flat-square)](https://www.npmjs.com/package/henri)\n[![npm downloads](https://img.shields.io/npm/dm/henri.svg?style=flat-square)](https://www.npmjs.com/package/henri)\n[![Build Status](https://travis-ci.org/usehenri/henri.svg?branch=master)](https://travis-ci.org/usehenri/henri)\n[![Coverage Status](https://coveralls.io/repos/github/usehenri/henri/badge.svg)](https://coveralls.io/github/usehenri/henri)\n[![Join slack](https://img.shields.io/badge/slack-get_invite-green.svg?longCache=true\u0026style=flat)](https://join.slack.com/t/usehenri/shared_invite/enQtNDU5Njg4MTA2OTY2LTU2N2I4MTVkNzQ4M2JlZTk5ZTIwODU1YTQxMzVmOTE2ZGVhZjNlZWY1ZTE2MWQxMDViZWY3ODY5ZjQwYzJiM2U)\n\nhenri is an easy to learn rails-like, server-side rendered (react \u0026 vue) with powerful ORMs\n\n- [How to use](#how-to-use)\n- [Contributing](#contributing)\n- [Configuration](#configuration)\n- [Models](#models)\n  - [Disk](#disk)\n  - [MongoDB](#mongodb)\n  - [MySQL](#mysql)\n  - [MSSQL](#mssql)\n  - [PostgreSQL](#PostgreSQL)\n- [GraphQL](#graphql)\n- [Views](#views)\n  - [React](#react)\n    - [Inferno](#inferno)\n    - [Preact](#preact)\n  - [Vue.js](#vue)\n  - [Handlebars](#handlebars)\n  - [Fetching data again](#fetching-data-again)\n- [Controllers](#controllers)\n- [Routes](#routes)\n  - [Roles](#roles)\n  - [CRUD](#crud)\n  - [Resources](#resources)\n  - [Scope](#scope)\n- [Mail](#mail)\n- [Workers](#workers)\n- [Under the hood](#under-the-hood)\n- [Plans, plans!](#plans)\n\n## How to use\n\n### Install\n\n```bash\n  yarn global add henri\n\n  # or\n\n  npm install -g henri\n```\n\n### Create a new project\n\n```bash\n  henri new \u003cfolder name\u003e\n```\n\nThe above command will create a directory structure similar to this:\n\n```shell\n├── app\n│   ├── controllers\n│   ├── helpers\n│   ├── models\n│   └── views\n│       ├── assets\n│       ├── components\n│       ├── pages\n│       ├── public\n│       │   ├── css\n│       │   ├── fonts\n│       │   ├── img\n│       │   ├── js\n│       │   └── patterns\n│       └── styles\n├── config\n│   ├── default.json\n│   ├── production.json\n│   ├── routes.js\n│   └── webpack.js            \u003c- Overload Next.js webpack settings\n├── test\n│   ├── controllers\n│   ├── helpers\n│   ├── models\n│   └── views\n├── package.json\n```\n\nIf you have a _Ruby on Rails_ background, this might look familiar.\n\nOne last step to start coding is:\n\n```bash\n  cd \u003cfolder name\u003e\n  henri server\n```\n\nAnd you're good to go!\n\n## Configuration\n\nThe configuration is a json file located in the `config` directory.\n\nhenri will try to load the file matching your `NODE_ENV` and will fallback to `default`.\n\nYou can have a `default.json`, `production.json`, etc.\n\n```json\n{\n  \"stores\": {\n    \"default\": {\n      \"adapter\": \"mongoose\",\n      \"url\": \"mongodb://user:pass@mongoserver.com:10914/henri-test\"\n    },\n    \"dev\": {\n      \"adapter\": \"disk\"\n    }\n  },\n  \"secret\": \"25bb9ed0b0c44cc3549f1a09fc082a1aa3ec91fbd4ce9a090b\",\n  \"renderer\": \"react\"\n}\n```\n\n## Models\n\nYou can easily add models under `app/models`.\n\nThey will be autoloaded and available throughout your application (exposed globally).\n\nWe use [Mongoose](http://mongoosejs.com/) for MongoDB, [Sequelize](http://docs.sequelizejs.com/) for SQL adapters and [Waterline](https://github.com/balderdashy/waterline) for the disk adapter.\n\nYou can use the command-line to generate models:\n\n```js\n# henri g model modelname name:string! age:number notes:string birthday:date!\n```\n\n```js\n// app/models/User.js\n\n// Whenever you have a User model, it will be overloaded with the following:\n\n// email: string\n// password: string\n// beforeCreate: encrypts the password\n// beforeUpdate: encrypts the password\n\nmodule.exports = {\n  store: 'dev', // see the demo configuration up there\n  name: 'user_collection', // will use user_collection' instead of 'users'\n  schema: {\n    firstName: { type: 'string' },\n    lastName: String,\n    tasks: {},\n  },\n};\n```\n\n```js\n// app/models/Tasks.js\n\nmodule.exports = {\n  store: 'default', // see the demo configuration up there\n  schema: {\n    name: { type: 'string', required: true },\n    category: {\n      type: 'string',\n      validations: {\n        isIn: ['urgent', 'high', 'medium', 'low'],\n      },\n      defaultsTo: 'low',\n    },\n  },\n};\n```\n\n### Disk\n\nThe disk adapter is using [Waterline](https://github.com/balderdashy/waterline) to provide disk-based storage.\n\nThis is not for production and you can easily port your models to other adapters.\n\n```bash\n  yarn add @usehenri/disk\n\n  # or\n\n  npm install @usehenri/disk --save\n```\n\n### MongoDB\n\nThe MongoDB adapter is using [Mongoose](http://mongoosejs.com/) to provide a MongoDB ODM.\n\n```bash\n  yarn add @usehenri/mongoose\n\n  # or\n\n  npm install @usehenri/mongoose --save\n```\n\n### MySQL\n\nThe MySQL adapter is using [Sequelize](http://docs.sequelizejs.com/) to provide a MySQL ORM.\n\n```bash\n  yarn add @usehenri/mysql\n\n  # or\n\n  npm install @usehenri/mysql --save\n```\n\n### MSSQL\n\nThe MSSQL adapter is using [Sequelize](http://docs.sequelizejs.com/) to provide a MSSQL ORM.\n\n```bash\n  yarn add @usehenri/mssql\n\n  # or\n\n  npm install @usehenri/mssql --save\n```\n\n### PostgreSQL\n\nThe PostgresQL adapter is also using [Sequelize](http://docs.sequelizejs.com/) to provide a PostgresQL ORM.\n\n```bash\n  yarn add @usehenri/postgresql\n\n  # or\n\n  npm install @usehenri/postgresql --save\n```\n\n## GraphQL\n\nYou can add a `graphql` key to your schema file and they will be automatically loaded, merged and available.\n\n### Definition\n\n```js\n// app/models/Task.js\n\nconst types = require('@usehenri/mongoose/types');\n\nmodule.exports = {\n  schema: {\n    description: { type: types.STRING, required: true },\n    type: { type: types.ObjectId, ref: 'Type', required: true },\n    location: { type: types.ObjectId, ref: 'Location', required: true },\n    reference: { type: types.STRING, required: true },\n    notes: { type: types.STRING },\n    oos: { type: types.BOOLEAN, default: false },\n  },\n  options: {\n    timestamps: true,\n  },\n  graphql: {\n    types: `\n      type Task {\n        _id: ID!\n        reference: String!\n        description: String!\n        location: Location\n        type: Type\n        notes: String!\n        oos: Boolean\n      }\n      type Query {\n        tasks: [Task]\n        task(_id: ID!): Task\n      }\n    `,\n    resolvers: {\n      Query: {\n        tasks: async () =\u003e {\n          return Task.find()\n            .populate('type location')\n            .exec();\n        },\n        task: async (_, id) =\u003e await Task.findOne(id).populate('type'),\n      },\n    },\n  },\n};\n```\n\n### Query\n\nYou will be able to query this anywhere. Even as an argument to `res.render()`. See below:\n\n```js\n// app/controllers/tasks.js\n\n// henri has a gql function which does nothing but help editors parse gql...!\nconst { gql } = henri;\n\nmodule.exports = {\n  index: async (req, res) =\u003e {\n    return res.render('/tasks', {\n      graphql: gql`\n        {\n          tasks {\n            _id\n            reference\n            description\n            type {\n              _id\n              name\n              prefix\n              counter\n            }\n            location {\n              _id\n              name\n            }\n          }\n          locations {\n            _id\n            name\n          }\n        }\n      `,\n    });\n  },\n};\n```\n\n## Views\n\nYou can use [React](#react), [Vue](#vue) and [Handlebars](#handlebars) as renderer. They are all server-side rendered and the first two options use webpack to push updates to the browser.\n\n### React\n\nWe use [next.js](https://github.com/zeit/next.js) to render pages and inject\ndata from controllers. You can only add pages and if the defined routes don't\nmatch, and next matches a route, it will be rendered.\n\nUsage (config file):\n\n```json\n{\n  \"renderer\": \"react\"\n}\n```\n\nExample:\n\n```jsx\n// app/views/pages/log.js\n\nimport React from 'react';\nimport Link from 'next/link';\n\nexport default data =\u003e (\n  \u003cdiv\u003e\n    \u003cdiv\u003e{data}\u003c/div\u003e\n    \u003cLink href=\"/home\"\u003e\n      \u003ca\u003eHome\u003c/a\u003e\n    \u003c/Link\u003e\n  \u003c/div\u003e\n);\n```\n\nYou can also add webpack configuration in `config/webpack.js`:\n\n```js\n// If you want to have jQuery as a global...\n\nmodule.exports = {\n  webpack: async (config, { dev }, webpack) =\u003e {\n    config.plugins.push(\n      new webpack.ProvidePlugin({\n        $: 'jquery',\n        jQuery: 'jquery',\n      })\n    );\n    return config;\n  },\n};\n```\n\n#### Inferno\n\nYou can use Inferno instead of React in production. In development, React will be used for hot re/loading.\n\nInstallation:\n\n```bash\nyarn add react react-dom inferno inferno-compat inferno-server\n```\n\nUsage (config file):\n\n```json\n{\n  \"renderer\": \"inferno\"\n}\n```\n\n#### Preact\n\nYou can use Preact instead of React in production. In development, React will be used for hot re/loading.\n\nInstallation:\n\n```bash\nyarn add react react-dom preact preact-compat\n```\n\nUsage (config file):\n\n```json\n{\n  \"renderer\": \"preact\"\n}\n```\n\n### Vue.js\n\nWe use [Nuxt.js](https://nuxtjs.org/) to render pages and inject\ndata from controllers. You can only add pages and if the defined routes don't\nmatch, and nuxt matches a route, it will be rendered.\n\nUsage (config file):\n\n```json\n{\n  \"renderer\": \"vue\"\n}\n```\n\nExample:\n\n```vue\n\u003ctemplate\u003e\n  \u003cdiv\u003e\n    \u003ch1\u003eWelcome!\u003c/h1\u003e\n    \u003cnuxt-link to=\"/about\"\u003eAbout page\u003c/nuxt-link\u003e\n  \u003c/div\u003e\n\u003c/template\u003e\n```\n\n### Handlebars\n\nThe handlebars options renders your `.html` or `.hbs` files under `app/views/pages`.\n\nIt will also load partials from `app/views/partials`\n\nUsage (config file):\n\n```json\n{\n  \"renderer\": \"template\"\n}\n```\n\nExample:\n\n```twig\n\u003chtml\u003e\n\n\u003chead\u003e\n  \u003ctitle\u003eHello!\u003c/title\u003e\n\u003c/head\u003e\n\n\u003cbody\u003e\n  {{\u003e somePartials }}\n  \u003cli\u003eSome data: {{hello}}\u003c/li\u003e\n\u003c/body\u003e\n\n\u003c/html\u003e\n```\n\n### Fetching data again\n\nYou can refetch data from any data-hydrated controller endpoint with GET using the `application/json` header.\n\n## Controllers\n\nYou can easily add controllers under `app/controllers`.\n\nThey will be autoloaded and available throughout your application.\n\nControllers are auto-reloaded on save.\n\n```js\n// app/controllers/User.js\n\nmodule.exports = {\n  info: async (req, res) =\u003e {\n    if (!req.isAuthenticated()) {\n      return res.status(403).send(\"Sorry! You can't see that.\");\n    }\n    const { user } = henri;\n    if (await User.count({ email: 'felix@usehenri.io' })) {\n      await User.update({ email: 'felix@usehenri.io' }, { password: 'blue' });\n      return res.send('user exists.');\n    }\n    try {\n      await user.compare('moo', pass);\n      res.send('logged in!');\n    } catch (error) {\n      res.send('not good');\n    }\n  },\n  create: (req, res) =\u003e {\n    await User.create({ email: 'felix@usehenri.io', password: 'moo' });\n  },\n  fetch: async (req, res) =\u003e {\n    const users = await User.find();\n    res.send(users);\n  },\n  postinfo: async (req, res) =\u003e {\n    let data = req.isAuthenticated() ? await User.find() : {};\n    res.render('/log', data);\n  }\n};\n```\n\n## Routes\n\nRoutes are defined in `config/routes.js`. Also, any pages in `app/views/pages` will\nbe rendered if no routes match before.\n\nRoutes are a simple object with a key standing as a route or an action verb\n(used by express) and a route.\n\nIf you want to access the `res.render` data, you can make the call with\n`application/json` header. Everything else will be rendered.\n\n```js\n// config/routes.js\n\nmodule.exports = {\n  '/test': 'user#info', // default to 'get /test'\n\n  '/abc/:id': 'moo#iii', // as this controller does not exists, route won't be loaded\n\n  '/user/find': 'user#fetch',\n\n  'get /poo': 'user#postinfo',\n\n  'post /poo': 'user#create',\n\n  'get /secured': {\n    controller: 'secureController#index',\n    roles: ['admin'],\n  },\n\n  'resources todo': {\n    controller: 'todo',\n  },\n\n  'crud categories': {\n    scope: 'api',\n    controller: 'categories',\n    omit: ['destroy'], // DELETE route will not be loaded\n  },\n};\n```\n\n### Roles\n\nYou can specify an array of roles which need to be matched to access the routes.\n\n### CRUD\n\nThe `crud` keyword (instead of http verbs) will create routes in a predefined way:\n\n```js\n// 'crud happy': 'life'\n\nGET /happy =\u003e life#index\nPOST /happy =\u003e life#create\nPATCH /happy/:id =\u003e life#update\nPUT /happy/:id =\u003e life#update\nDELETE /happy/:id =\u003e life#destroy\n```\n\n### Resources\n\nThe `resources` keyword (instead of http verbs) add views target to CRUD, ending up with:\n\n```js\n// 'resources happy': 'life'\n\nGET /happy =\u003e life#index\nPOST /happy =\u003e life#create\nPATCH /happy/:id =\u003e life#update\nPUT /happy/:id =\u003e life#update\nDELETE /happy/:id =\u003e life#destroy\n\nGET /happy/:id/edit =\u003e life#edit\nGET /happy/new =\u003e life#new\nGET /happy/:id =\u003e life#show\n```\n\n### Scope\n\nYou can add `scope` to your routes to prefix them with anything you want.\n\n### Omit (crud \u0026 resources only)\n\nYou can add `omit` array to your routes to prevent this route to be created.\n\n## Mail\n\nWe use [nodemailer](https://nodemailer.com) to provide email capabilities.\n\nWhen running tests, we use nodemailer's ethereal fake-mail service.\n\n### Config\n\n```json\n{\n  \"mail\": {\n    // ...Same as nodemailer's config\n  }\n}\n```\n\n### Send\n\nWe provide a wrapper around `nodemailer.SendMail`:\n\n```js\nawait henri.mail.send({\n  from: '\"Henri Server\" \u003cfoo@example.com\u003e', // sender address\n  to: 'bar@example.com, baz@example.com', // list of receivers\n  subject: 'Hello ✔', // Subject line\n  text: 'Hello world?', // plain text body\n  html: '\u003cb\u003eHello world?\u003c/b\u003e', // html body\n});\n```\n\nIf you are using the test accounts, you will see a link to your email in the console.\n\nYou can access nodemailer's package directly from `henri.mail.nodemailer` and\ntransporter from `henri.mail.transporter`.\n\n## Workers\n\nYou can add files under `app/workers` and they will be auto-loaded, watched and reloaded.\n\nIf they export a `start()` and a `stop()` method, they will be call when initializing and tearing down (reload also).\n\nExample:\n\n```js\nlet timer;\n\nconst start = h =\u003e {\n  h.pen.info('worker started');\n  timer = setInterval(\n    () =\u003e h.pen.warn(`the argument is the henri object`),\n    5000\n  );\n};\n\nconst stop = () =\u003e clearInterval(timer);\n\nmodule.exports = { start, stop };\n```\n\n## Under the hood\n\n### Vision\n\n_Bundle the best tools in a structured environment to provide a stable and fast-paced development experience._\n\n### Modules\n\n_We use a 8 levels boot system._\n\n1.  All modules are scanned and put in a sequence with same-level modules\n\n2.  We cycle from level 0 to 7, initializing all the same-level modules in a concurrent way\n\n3.  If the module is reloadable, it will unwind and rewind in the same sequence on reloads\n\nSee the [Contributing](#contributing) section for more information\n\n## Plans\n\n- Add helpers integration\n- Add documentation!\n- Build a website\n- Report bugs!\n\n## Contributing\n\n- Step by step wiki [here](https://github.com/usehenri/henri/wiki/Contributing)\n\n## Thanks to the following and their contributors\n\n- [Next.js](https://github.com/zeit/next.js)\n- [Express](https://expressjs.com/)\n\n## Author\n\n- Félix-Antoine Paradis ([@reel](https://github.com/reel))\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fusehenri%2Fhenri","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fusehenri%2Fhenri","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fusehenri%2Fhenri/lists"}