{"id":28348703,"url":"https://github.com/panta82/crud_ui","last_synced_at":"2026-02-09T16:33:12.724Z","repository":{"id":81161180,"uuid":"198289375","full_name":"panta82/crud_ui","owner":"panta82","description":"A \"CMS-light\" express.js router that serves UI for a single CRUD resource.","archived":false,"fork":false,"pushed_at":"2021-03-12T18:06:48.000Z","size":1444,"stargazers_count":2,"open_issues_count":2,"forks_count":1,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-11-13T04:23:46.441Z","etag":null,"topics":["cms","crud","express","express-router","nodejs"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","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/panta82.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-07-22T19:29:05.000Z","updated_at":"2022-03-21T05:19:35.000Z","dependencies_parsed_at":"2023-04-04T00:32:56.217Z","dependency_job_id":null,"html_url":"https://github.com/panta82/crud_ui","commit_stats":{"total_commits":73,"total_committers":2,"mean_commits":36.5,"dds":"0.12328767123287676","last_synced_commit":"a7aaf32262e0f534f3d6be3528f5e18a5919195e"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/panta82/crud_ui","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/panta82%2Fcrud_ui","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/panta82%2Fcrud_ui/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/panta82%2Fcrud_ui/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/panta82%2Fcrud_ui/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/panta82","download_url":"https://codeload.github.com/panta82/crud_ui/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/panta82%2Fcrud_ui/sbom","scorecard":{"id":719434,"data":{"date":"2025-08-11","repo":{"name":"github.com/panta82/crud_ui","commit":"a7aaf32262e0f534f3d6be3528f5e18a5919195e"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.6,"checks":[{"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":"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":"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":"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":"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":"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":"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":"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":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"License","score":0,"reason":"license file not detected","details":["Warn: project does not have a license file"],"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":"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"}}]},"last_synced_at":"2025-08-22T10:50:16.168Z","repository_id":81161180,"created_at":"2025-08-22T10:50:16.168Z","updated_at":"2025-08-22T10:50:16.168Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29272856,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-09T13:47:44.167Z","status":"ssl_error","status_checked_at":"2026-02-09T13:47:43.721Z","response_time":56,"last_error":"SSL_read: 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":["cms","crud","express","express-router","nodejs"],"created_at":"2025-05-27T19:08:32.718Z","updated_at":"2026-02-09T16:33:12.719Z","avatar_url":"https://github.com/panta82.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# CRUD UI\n\nA \"CMS-light\" express.js router that serves UI for a single CRUD resource.\n\n```bash\nnpm install --save crud-ui\n```\n\n### Simple example\n\n```javascript\nconst http = require('http');\nconst express = require('express');\n\nconst crudUI = require('crud-ui');\n\nconst app = express();\n\napp.use(\n  '/',\n  crudUI({\n    name: 'user',\n    recordId: 'id',\n    fields: [\n      {\n        name: 'name',\n        label: 'Name',\n      },\n      {\n        name: 'phone',\n        label: 'Phone number',\n      },\n    ],\n    actions: {\n      getList: () =\u003e {\n        return [\n          { name: 'Mark', phone: '+123 456 789' },\n          { name: 'Milo', phone: '' },\n          { name: 'Sandra', phone: '+1 33 55 66' },\n        ];\n      },\n    },\n  })\n);\n\nhttp.createServer(app).listen(3000);\n```\n\n![Minimal example](misc/cui_1.png)\n\n### More details\n\nThe UI is rendered server-side using a vanilla [bootstrap](https://getbootstrap.com/) layout (feel free to slap your own [theme](https://bootswatch.com/) on top of it). Validation is performed by [validate.js](https://validatejs.org/), but you can plug in your own library. Every part of the UI in general is overridable through options.\n\nBy default, each crudUI router serves a single REST-like resource - a table view, with links to each item's detail page, edit page and delete popup (there are other view modes too, eg. the *single record mode*). You can use multiple crudUI handlers to work with multiple resources. We provide an option of adding a top level menu, so you can navigate between them (and/or other parts of your app).\n\nAs the name says, this library is for the UI layer only. Since you are providing functions which resolve various operations, you can plug whatever database or other backend you want. We also don't do authentication. It is recommended to plug in your own auth middleware before the crudUI handler is reached.\n\nTo summarize:\n\n##### This library is for you if:\n\n- You already have a node.js app and just want to plug in a little configuration screen so that sales people can tweak a few options or something.\n\n- You don't want CMS to dictate your backend. You are using a 3rd party store, already have a database or just want to structure your backend and API how you like it.\n\n- You have straightforward UI requirements - basic server-side rendered CRUD with flat objects.\n\n- You have relatively little data to manage (we don't support pagination yet).\n\n- You like self-contained low-impact libraries. We have very few direct dependencies, on top of 2 required peer dependencies (`express.js` and `body-parser`).\n\n##### This library is NOT for you if:\n\n- You want a fully managed CMS solution with minimal coding.\n\n- You want to serve an interface towards customers. Our UI is functional, but simple and generic looking. While you can overwrite every view with your own code, if you start doing that all the time, maybe it's time to put up a real UI :-)\n\n- You have a complicated object model, requiring advanced entity relations or crazy tree views\n\n- You want to make a blog. We don't have a WYSIWYG editor yet.\n\n### Advanced example\n\n```javascript\nconst http = require('http');\nconst express = require('express');\n\nconst port = process.env.PORT || 3000;\n\nconst { crudUI, CUIField, FIELD_TYPES, CUI_MODES } = require('crud-ui');\n\nconst app = express();\n\nconst data = [\n  { id: 1, name: 'Axe' },\n  { id: 2, name: 'Barry', description: 'This\\nIs\\nBarry!' },\n  { id: 3, name: 'Cindy', gender: 'female' },\n];\n\napp.use(\n  '/admin/users',\n  crudUI({\n    name: 'user',\n    mode: CUI_MODES.simple_list,\n    recordId: 'id',\n    navigation: {\n      brand: {\n        title: 'Tester',\n        url: '/admin/users',\n      },\n      left: [\n        {\n          title: 'Users',\n          url: '/admin/users',\n        },\n        {\n          title: 'Projects',\n          url: '/admin/projects',\n        },\n      ],\n      right: [\n        {\n          title: 'User',\n          items: [\n            {\n              title: 'Home',\n              url: '/admin/users',\n            },\n            {\n              title: '---',\n            },\n            {\n              render: (/** CUIContext */ ctx) =\u003e {\n                return `\u003cbutton class=\"dropdown-item\" onclick=\"alert('logout')\"\u003eLog out\u003c/button\u003e`;\n              },\n            },\n          ],\n        },\n      ],\n    },\n    fields: [\n      new CUIField({\n        type: FIELD_TYPES.string,\n        name: 'id',\n        label: 'ID',\n        noEdit: true,\n      }),\n      new CUIField({\n        type: FIELD_TYPES.string,\n        name: 'name',\n        label: 'Name',\n        helpText: \"Person's full name and surname\",\n        validate: {\n          presence: {\n            allowEmpty: false,\n          },\n        },\n        validateEdit: {\n          length: { minimum: 20 },\n        },\n      }),\n      new CUIField({\n        type: FIELD_TYPES.text,\n        name: 'description',\n        label: 'Description',\n        validateCreate: (ctx, val) =\u003e {\n          if (val.indexOf('cheese') \u003c 0) {\n            return 'must contain word cheese';\n          }\n        },\n      }),\n      new CUIField({\n        type: FIELD_TYPES.select,\n        name: 'gender',\n        label: 'Gender',\n        values: ['male', 'female', 'other'],\n        nullOption: true\n      }),\n    ],\n    actions: {\n      getList: ctx =\u003e {\n        return data;\n      },\n      getSingle: (ctx, id) =\u003e {\n        return data.find(item =\u003e String(item.id) === String(id));\n      },\n      create: (ctx, payload) =\u003e {\n        const id = data.reduce((max, item) =\u003e Math.max(item.id, max), 0) + 1;\n        const item = { ...payload, id };\n        data.push(item);\n        return item;\n      },\n      update: (ctx, id, payload) =\u003e {\n        const existing = data.find(item =\u003e String(item.id) === String(id));\n        if (!existing) {\n          throw new Error(`Not found: ${id}`);\n        }\n        Object.assign(existing, payload);\n        return existing;\n      },\n      delete: (ctx, id) =\u003e {\n        const index = data.findIndex(item =\u003e String(item.id) === String(id));\n        if (index \u003c 0) {\n          throw new Error(`Not found: ${id}`);\n        }\n        const item = data.splice(index, 1)[0];\n        return item;\n      },\n    },\n  })\n);\n\napp.get('/', (req, res) =\u003e {\n  return res.set('content-type', 'text/html').send(`\n  \u003cbody\u003e\n    \u003ch1\u003eThis is just a tester\u003c/h1\u003e\n    \u003ch4\u003e\n      \u003ca href=\"/admin/users\"\u003eGo to admin\u003c/a\u003e\n    \u003c/h4\u003e\n  \u003c/body\u003e\n  `);\n});\n\nconst server = http.createServer(app);\nserver.listen(port, () =\u003e {\n  console.log(`Listening on http://localhost:${port}`);\n});\n```\n\n![Advanced example 1](misc/cui_2.png)\n\n![Advanced example 2](misc/cui_3.png)\n\n![Advanced example 3](misc/cui_4.png)\n\nFor an even bigger example, look into the [kitchen_sink tester app](testers/kitchen_sink.js).\n\n### Development\n\nFollow the development and TODO-s here: \u003chttps://trello.com/b/3vSgeUxa/crudui\u003e\n\nRelease log can be found [here](docs/releases.md).\n\n**NOTE:** I use this project in production for one micro CMS, and it's working fine. Still, given its lack of unit tests and relative immaturity, *use at your own risk*.\n\n### License\n\n[MIT](https://opensource.org/licenses/MIT)\n \n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpanta82%2Fcrud_ui","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpanta82%2Fcrud_ui","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpanta82%2Fcrud_ui/lists"}