{"id":14957639,"url":"https://github.com/mamena2020/nodemi","last_synced_at":"2025-08-18T11:10:52.177Z","repository":{"id":65759579,"uuid":"592635312","full_name":"Mamena2020/nodemi","owner":"Mamena2020","description":"Boilerplate for Nodejs. Handling routing, Rule validation, ORM, Media library, File request handling, Jwt auth, Role \u0026 Permissions, Resources, Locale, Mail, Seeder \u0026 more..","archived":false,"fork":false,"pushed_at":"2025-07-11T11:23:09.000Z","size":437,"stargazers_count":39,"open_issues_count":0,"forks_count":10,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-07-11T14:03:10.542Z","etag":null,"topics":["backend","backend-api","backend-nodejs","backend-template","boilerplate","boilerplate-backend","boilerplate-node","boilerplate-nodejs","express-js","jwt-auth","node-orm","node-resources","node-validator","nodejs","role-and-permission","sequelize-orm","validatorjs"],"latest_commit_sha":null,"homepage":"","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/Mamena2020.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","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":"2023-01-24T07:13:05.000Z","updated_at":"2025-07-11T11:23:12.000Z","dependencies_parsed_at":null,"dependency_job_id":"4d9fb600-4554-4cc7-b6e7-27ca75ecbe43","html_url":"https://github.com/Mamena2020/nodemi","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/Mamena2020/nodemi","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Mamena2020%2Fnodemi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Mamena2020%2Fnodemi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Mamena2020%2Fnodemi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Mamena2020%2Fnodemi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Mamena2020","download_url":"https://codeload.github.com/Mamena2020/nodemi/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Mamena2020%2Fnodemi/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":270982201,"owners_count":24679449,"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-08-18T02:00:08.743Z","response_time":89,"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":["backend","backend-api","backend-nodejs","backend-template","boilerplate","boilerplate-backend","boilerplate-node","boilerplate-nodejs","express-js","jwt-auth","node-orm","node-resources","node-validator","nodejs","role-and-permission","sequelize-orm","validatorjs"],"created_at":"2024-09-24T13:15:16.721Z","updated_at":"2025-08-18T11:10:52.158Z","avatar_url":"https://github.com/Mamena2020.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Nodemi\n\nBoilerplate for nodejs. base on express js.\n\n- ### Features\n\n  - Model - ORM\n\n    Create model via cli and make relation between.\n\n  - Media library\n\n    Binding media to any Model, so any model can own the media, and will able to save media, get media, and destroy media.\n    Media can be stored to `Local storage` or `Firebase Storage`.\n\n  - File request handling\n\n    Not worry about handling uploaded file, just upload from client side, and you can access file in request, ex: `req.body.avatar`.\n\n  - Request validation\n\n    Determine if request passes the rule.\n    You can create `custom rule` via cli.\n\n  - Role and Permissions\n\n    Binding to any model, any model can have a role and permissions, set role, checking access.\n\n  - Resources\n\n    Create custom resource from resources.\n\n  - Auth - JWT/Basic Auth\n\n    Create token, re generate token, and set middleware authorization for certain routes.\n\n  - Locale\n\n    Enabled or disabled locale or just enabled on certain routes.\n\n  - Mail\n\n    Create mail via cli, and send mail with html, file, or just text.\n\n  - Firebase Cloud Messaging\n\n    Sending push notification from server to client device.\n\n  - Seeder\n\n    Running seeder via cli.\n\n- ### Live demo\n\n  | Action          | Method | Auth   | Body             | EndPoint                                              |\n  | --------------- | ------ | ------ | ---------------- | ----------------------------------------------------- |\n  | Login           | POST   |        | email            | https://nodemi.onrender.com/api/login                 |\n  |                 |        |        | password         |                                                       |\n  |                 |        |        |                  |                                                       |\n  | Register        | POST   |        | email            | https://nodemi.onrender.com/api/register              |\n  |                 |        |        | name             |                                                       |\n  |                 |        |        | password         |                                                       |\n  |                 |        |        | confirm_password |                                                       |\n  |                 |        |        |                  |                                                       |\n  | Token           | GET    |        |                  | https://nodemi.onrender.com/api/token                 |\n  |                 |        |        |                  |                                                       |\n  |                 |        |        |                  |                                                       |\n  | Logout          | DELETE |        |                  | https://nodemi.onrender.com/api/logout                |\n  |                 |        |        |                  |                                                       |\n  | Get User        | GET    | Bearer |                  | https://nodemi.onrender.com/api/user                  |\n  |                 |        | token  |                  |                                                       |\n  |                 |        |        |                  |                                                       |\n  | Get Users       | GET    | Bearer |                  | https://nodemi.onrender.com/api/users                 |\n  |                 |        | token  |                  |                                                       |\n  |                 |        |        |                  |                                                       |\n  | Forgot Password | POST   |        | email            | https://nodemi.onrender.com/api/forgot-password       |\n  |                 |        |        |                  |                                                       |\n  | Reset Password  | PUT    |        | new_password     | https://nodemi.onrender.com/api/reset-password/:token |\n\n# Getting Started\n\n- Clone this repo `https` or `SSH`\n\nClone and move to directory project and run `npm install`\n\n``` \n   git clone git@github.com:Mamena2020/nodemi.git\n\n```\n\n- ### Create database\n\nCreate database `mysql` or `pgsql`.\n\n``` \n   #mysql example\n\n   mysql -u root -p\n   # enter your password\n\n   create database mydatabase;\n\n   #pgsql example\n\n   createdb -h localhost -p 5432 -U myPgUser mydatabase\n\n```\n\n- ### Setup .env\n\nAfter creating your database, you can fill in the .env file and start your code.\n\n``` \n   cp .env.example .env\n\n```\n\n# Model\n\n- ### Create new model via cli.\n\n```\n   npx nodemi make:model Product\n```\n\nThe model will be created in the `models` directory.\n\n``` js\n\n   import { Model, DataTypes } from \"sequelize\";\n   import db from \"../core/database/Database.js\"\n\n   class Product extends Model {}\n   Product.init({\n        name: {\n          type: DataTypes.STRING,\n          allowNull: false\n        },\n      }, {\n        sequelize: db,\n        tableName: 'products',\n        modelName: 'Product',\n        timestamps: true\n      }\n   );\n\n   export default Product\n\n```\n\nAutomatically registered in the `loadModels` function in the `models/Models.js` file.\n\n``` js\n\n   const loadModels = async () =\u003e {\n\n      await Product.sync({\n          alter: true, // not recomended on production mode\n      })\n\n       ....\n\n```\n\nFull \u003ca target=\"_blank\" href=\"https://sequelize.org/docs/v6/core-concepts/model-basics/\"\u003e documentation \u003c/a\u003e of ORM\n\n- ### Noted\n\nAll relationships between models should be defined in the `loadModels` function.\nWhen a model is removed from the `models` directory, it is important to also remove its corresponding relationship from the `loadModels` function in the `models/Models.js` file.\n\n# Media\n\nAny model can own media by binding the model to the media inside the `loadModels` function using `hasMedia(YourModel)`.\n\n``` js\n\n   const loadModels = async () =\u003e {\n\n      await Product.sync({\n          alter: true, // not recomended on production mode\n      })\n\n      await hasMedia(Product)\n\n```\n\n- ### Save a file\n\nAfter binding model using `hasMedia(YourModel)`, then your model will able to save a file using `instance.saveMedia(file,mediaName)`. If the instance already has a file with the same name, then the file will be replaced with a new file.\n\n``` js\n\n   const product = await Product.findOne({\n       where: {\n             id: 1\n       }\n   })\n\n   await product.saveMedia(req.body.file,\"thumbnail\") // if success then will return media object\n\n```\n\nYou can save files to either `Local` storage or `Firebase` storage.\n\nTo save to `Local` storage, just set your .env file `MEDIA_STORAGE=local` , and local storage directory name `MEDIA_LOCAL_STORAGE_DIR_NAME=storage`.\n\n``` env\n   MEDIA_STORAGE=local\n   MEDIA_LOCAL_STORAGE_DIR_NAME=storage\n```\n\nTo save to `Firebase` storage, first create your `Service Account .json` on firebase \u003ca href=\"https://console.firebase.google.com/\"\u003eFirebase Console\u003c/a\u003e, and download and convert to `base64` string, then setup the .env file.\n\n``` env\n   MEDIA_STORAGE=firebase  # set to firebase\n   FIREBASE_STORAGE_BUCKET=gs://xxxxxx.appspot.com  # your firebase storage bucket\n   FIREBASE_SERVICE_ACCOUNT_BASE64= # base64 string of your firebase service account .json\n```\n\n- ### Get media\n\nGet all media by calling `instance.getMedia()`.\n\n``` js\n\n   const product = await Product.findOne({\n       where: {\n             id: 1\n       }\n   })\n\n   product.getMedia() // return list of object\n\n\n```\n\nGet media by name, params is media name\n\n``` js\n    product.getMediaByName(\"thumbnail\") // return single object\n    product.getMediaByName(\"thumbnail\").url // return single object url\n\n```\n\nGet media first media\n\n``` js\n    product.getFirstMedia()       // return single object\n    product.getFirstMedia().url   // return first media url\n\n```\n\nGet media with exception, params can be `string` or `array` of string\n\n``` js\n    product.getMediaExcept(\"thumbnail_mobile\")  // return list of object with exception\n\n```\n\nGet all media url,\n\n``` js\n    product.getMediaUrl()  // return list of media url\n\n```\n\nGet all media url with exception, params can be `string` or `array` of string\n\n``` js\n    product.getMediaUrlExcept(['thumbnail_mobile'])  // return list of url\n\n```\n\nGet url from media object\n\n``` js\n    product.getFirstMedia().getUrl()\n\n```\n\n- ### Destroy media\n\nDestroy media by calling `instance.destroyMedia(mediaName)`. return status deleted in boolean\n\n``` js\n\n   const product = await Product.findOne({\n       where: {\n             id: 1\n       }\n   })\n\n   await product.destroyMedia(\"thumbnail\")\n\n```\n\n- ### Noted\n\nAll media files will be automatically deleted whenever `instance` of your model is deleted.\n\n# Request \u0026 Upload Files\n\nHandling Content-Type header for\n\n      - application/json\n      - application/form-data\n      - application/x-www-form-urlencoded\n\nHandling all upload files on `POST` and `PUT` method, and nested fields.\n\n- ### File properties\n\nUploaded file will have this properties.\n\n```\n\n     name           -\u003e file name,\n     encoding       -\u003e file encoding,\n     type           -\u003e file mimeType,\n     size           -\u003e file size,\n     sizeUnit       -\u003e file size in bytes\n     extension      -\u003e file extension\n     tempDir        -\u003e file temporary directory\n\n```\n\n# Rule Validation\n\n- ### Create Request validation via cli.\n\n```\n   npx nodemi make:request ProductRequest\n```\n\nThe Request will be created in the `requests` directory.\n\n``` js\n\n   import RequestValidation from \"../core/validation/RequestValidation.js\"\n\n   class ProductRequest extends RequestValidation {\n\n       constructor(req) {\n           super(req).load(this)\n       }\n\n        /**\n        * Get the validation rules that apply to the request.\n        *\n        * @return object\n        */\n       rules() {\n           return {\n\n           }\n       }\n    }\n\n    export default ProductRequest\n\n```\n\n- ### Basic usage.\n\n``` js\n\n   const request = new ProductRequest(req)\n\n   await request.check()\n\n   if (request.isError)\n      return request.responseError(res) // or  return res.status(422).json(request.errors)\n\n```\n\n- ### Example html form.\n\n``` html\n\n   \u003cform action=\"endpoint\" method=\"post\" enctype=\"multipart/form-data\"\u003e\n      \u003cdiv class=\"row justify-content-center d-flex\"\u003e\n\n         \u003cdiv class=\"col-md-10\"\u003e\n             \u003clabel\u003eItem\u003c/label\u003e\n             \u003cinput type=\"text\" name=\"name\" placeholder=\"name\" /\u003e\n             \u003cinput type=\"text\" name=\"discount\" placeholder=\"discount\" /\u003e\n             \u003cinput type=\"date\" name=\"expired_date\" placeholder=\"expired date\" /\u003e\n             \u003cinput type=\"file\" name=\"product_image\" placeholder=\"file\" /\u003e\n         \u003c/div\u003e\n         \u003cdiv class=\"col-md-10\"\u003e\n             \u003clabel\u003eItem 1\u003c/label\u003e\n             \u003cinput type=\"text\" name=\"item[0][name]\" placeholder=\"name\" /\u003e\n             \u003cinput type=\"text\" name=\"item[0][description]\" placeholder=\"description\" /\u003e\n             \u003cinput type=\"text\" name=\"price[0]\" placeholder=\"price \" /\u003e\n         \u003c/div\u003e\n         \u003cdiv class=\"col-md-10 mt-5\"\u003e\n             \u003clabel\u003eItem 2\u003c/label\u003e\n             \u003cinput type=\"text\" name=\"item[1][name]\" placeholder=\"name\" /\u003e\n             \u003cinput type=\"text\" name=\"item[1][description]\" placeholder=\"description\" /\u003e\n             \u003cinput type=\"text\" name=\"price[1]\" placeholder=\"price\" /\u003e\n         \u003c/div\u003e\n          \u003cdiv class=\"col-md-10 mt-5\"\u003e\n             \u003cinput type=\"text\" name=\"comments[]\" /\u003e\n             \u003cinput type=\"text\" name=\"comments[]\" /\u003e\n             \u003cinput type=\"text\" name=\"comments[]\" /\u003e\n         \u003c/div\u003e\n          \u003cdiv class=\"col-md-10 mt-5\"\u003e\n             \u003cinput type=\"text\" name=\"seo[title]\" value=\"\" placeholder=\"seo title\" /\u003e\n             \u003cinput type=\"text\" name=\"seo[description][long]\" value=\"\" placeholder=\"seo long desc\" /\u003e\n             \u003cinput type=\"text\" name=\"seo[description][short]\" value=\"\" placeholder=\"seo short desc\" /\u003e\n         \u003c/div\u003e\n\n\n         \u003cdiv class=\"col-md-10 my-2 \"\u003e\n             \u003cbutton class=\"float-end btn btn-primary\" type=\"submit\"\u003eSubmit\u003c/button\u003e\n         \u003c/div\u003e\n\n      \u003c/div\u003e\n   \u003c/form\u003e\n\n```\n\n- ### Example rules\n\n``` js\n\n   rules() {\n     return {\n         \"name\": {\n             \"rules\": [\"required\"]\n         },\n         \"discount\": {\n             \"rules\": [\"required\", \"float\", \"min:3\", \"max:4\"]\n         },\n         \"expired_date\": {\n             \"rules\": [\"required\", \"date\", \"date_after:now\"]\n         },\n         \"product_image\": {\n             \"rules\": [\"required\", \"image\", \"max_file:1,MB\"]\n         },\n         \"item.*.name\": {\n             \"rules\": [\"required\"]\n         },\n         \"item.*.description\": {\n             \"rules\": [\"required\"]\n         },\n         \"price.*\": {\n             \"rules\": [\"required\", \"float\"]\n         },\n         \"comments.*\": {\n             \"rules\": [\"required\"]\n         },\n         \"seo.title\": {\n             \"rules\": [\"required\"]\n         },\n         \"seo.description.long\": {\n             \"rules\": [\"required\"]\n         },\n         \"seo.description.short\": {\n             \"rules\": [\"required\"]\n         }\n     }\n   }\n\n```\n\n- ### Example error messages\n\n``` js\n\n   {\n     \"errors\": {\n          \"name\": [\n            \"The Name is required\"\n          ],\n          \"discount\": [\n            \"The Discount is required\",\n            \"The Discount must be valid format of float\",\n            \"The Discount should be more or equal than 3\",\n            \"The Discount should be less or equal than 4\"\n          ],\n          \"expired_date\": [\n            \"The Expired date is required\",\n            \"The Expired date must be valid format of date\",\n            \"The Expired date date must be after the now's date\"\n          ],\n          \"product_image\": [\n            \"The Product image is required\"\n          ],\n          \"item.0.name\": [\n            \"The Item.0.name is required\"\n          ],\n          \"item.1.name\": [\n            \"The Item.1.name is required\"\n          ],\n          \"item.0.description\": [\n            \"The Item.0.description is required\"\n          ],\n          \"item.1.description\": [\n            \"The Item.1.description is required\"\n          ],\n          \"price.0\": [\n            \"The Price.0 is required\",\n            \"The Price.0 must be valid format of float\"\n          ],\n          \"price.1\": [\n            \"The Price.1 is required\",\n            \"The Price.1 must be valid format of float\"\n          ],\n          \"comments.0\": [\n            \"The Comments.0 is required\"\n          ],\n          \"comments.1\": [\n            \"The Comments.1 is required\"\n          ],\n          \"comments.2\": [\n            \"The Comments.2 is required\"\n          ]\n          \"seo.title\": [\n            \"The Seo.title is required\"\n          ],\n          \"seo.description.long\": [\n            \"The Seo.description.long is required\"\n          ],\n          \"seo.description.short\": [\n            \"The Seo.description.short is required\"\n          ]\n      }\n   }\n\n```\n\n- ### Basic rules\n\n  | Rule                 | Description                                   | Example                                                     |\n  | -------------------- | --------------------------------------------- | ----------------------------------------------------------- |\n  | required             | check empty value                             | \"required\"                                                  |\n  | email                | check email formats                           | \"email\"                                                     |\n  | match                | check match value with other value            | \"match:password\"                                            |\n  | exists               | check value exists in the database            | \"exists:users,email\" or \"exists:users,email,\"+super.body.id |\n  | unique               | check value is unique in database             | \"unique:users,email\" or \"unique:users,email,\"+super.body.id |\n  | string               | check value is an string                      | \"string\"                                                    |\n  | float                | check value is an float                       | \"float\"                                                     |\n  | integer              | check value is an ineteger                    | \"integer\"                                                   |\n  | max                  | count maximum value of numeric,               | \"max:12\"                                                    |\n  |                      | if string/array its count the length          |                                                             |\n  | min                  | count minimum value of numeric,               | \"min:5\"                                                     |\n  |                      | if string/array its count the length          |                                                             |\n  | date                 | check value is date format                    | \"date\"                                                      |\n  | array                | check value is an array                       | \"array\"                                                     |\n  | mimetypes            | check file mimetypes                          | \"mimetypes:image/webp,image/x-icon,video/mp4\"               |\n  | mimes                | check file extension                          | \"mimes:jpg,png,jpeg\"                                        |\n  | max_file             | check maximum file size,                      | \"max_file:1,GB\" or \"max_file:1,MB\" or \"max_file:1,Byte\"     |\n  |                      | param can be `GB`, `MB`, `KB` or `Byte`       |                                                             |\n  | image                | check file is an image format                 | \"image\"                                                     |\n  | date_after           | check value after particular date             | \"date_after:now\" or \"date_after:birthdate\"                  |\n  |                      | param can be `now`, or other field name       |                                                             |\n  | date_after_or_equal  | check value after or equal particular date    | \"date_after_or_equal:now\"                                   |\n  |                      | param can be `now`, or other field name       |                                                             |\n  | date_before          | check value before particular date            | \"date_before:now\" or \"date_before:birthdate\"                |\n  |                      | param can be `now`, or other field name       |                                                             |\n  | date_before_or_equal | check value before or equal particular date   | \"date_before_or_equal:now\"                                  |\n  |                      | param can be `now`, or other field name       |                                                             |\n  | boolean              | check value is an boolean                     | \"boolean\"                                                   |\n  | in_array             | check value exist in array                    | \"in_array:1,3,4,1,4,5\"                                      |\n  | not_in_array         | check value is not include in array           | \"not_in_array:1,3,4,1,4,5\"                                  |\n  | ip                   | check value is as ip address                  | \"ip\"                                                        |\n  | url                  | check value is as url                         | \"url\"                                                       |\n  | json                 | check value is as json format                 | \"json\"                                                      |\n  | digits               | check value digits,                           | \"digits:4\"                                                  |\n  | max_digits           | check maximum digits of value                 | \"max_digits:20\"                                             |\n  | min_digits           | check minumum digits of value                 | \"min_digits:20\"                                             |\n  | digits_between       | check digits bewteen of value                 | \"digits_between:5,10\"                                       |\n  | age_lt               | check value is less than param                | \"age_lt:17\"                                                 |\n  |                      | value must be an date format                  |                                                             |\n  | age_lte              | check value is less than or equal to param    | \"age_lte:17\"                                                |\n  |                      | value must be an date format                  |                                                             |\n  | age_gt               | check value is greater than param             | \"age_gt:17\"                                                 |\n  |                      | value must be an date format                  |                                                             |\n  | age_gte              | check value is greater than or equal to param | \"age_gte:17\"                                                |\n  |                      | value must be an date format                  |                                                             |\n\n- ### Custom\n\nCustom validation `messages` and `attribute`\n\n``` js\n\n    rules() {\n        return {\n                \"name\": {\n                    \"rules\": [\"required\"],\n                    \"attribute\": \"Product name\"\n                },\n                \"discount\": {\n                    \"rules\": [\"required\", \"float\", \"min:3\", \"max:4\"],\n                    \"messages\": {\n                        \"required\": \"The _attribute_ need discount\",\n                        \"float\": \"The data must be numeric\"\n                    },\n                    \"attribute\": \"DISCOUNT\"\n                }\n            }\n    }\n\n```\n\n- ### Direct add error messages\n\nDirect add error message required key and error message.\n\n``` js\n    const request = new ProductRequest(req)\n    await request.check()\n\n    if (request.isError)\n    {\n        request.addError(\"name\",\"Name have to .....\")\n        request.addError(\"name\",\"Name must be .....\")\n\n\n```\n\n# Custom Rule\n\n- ### Create Custom Rule via cli.\n\n```\n   npx nodemi make:rule GmailRule\n```\n\nThe Rule will be created in the `rules` directory.\n\n``` js\n    class GmailRule  {\n\n        constructor() {\n        }\n\n        /**\n        * Determine if the validation rule passes.\n        * @param {*} attribute\n        * @param {*} value\n        * @returns bolean\n        */\n        passes(attribute, value) {\n\n            return value.includes(\"@gmail.com\")\n        }\n\n        /**\n        * Get the validation error message.\n        *\n        * @return string\n        */\n        message() {\n            return 'The _attribute_ must be using @gmail.com'\n        }\n   }\n\n   export default GmailRule\n\n```\n\n- ### Custom rule usage\n\n``` js\n    rules() {\n        return {\n                \"email\": {\n                    \"rules\": [ new GmailRule, \"required\",\"email\" ]\n                }\n            }\n    }\n```\n\n- ### Noted\n\nDefault error messages outputs are dependent on the locale. If you haven't set up the locale as a middleware, it will be set to English `en` by default.\n\n# Role and Permissions\n\nA user model can have a role by binding using `hasRole(YourModel)` function inside `loadModels` in `models/Models.js` file.\n\n``` js\n\n   const loadModels = async () =\u003e {\n\n      await User.sync()\n\n      await hasRole(User)\n\n```\n\n- ### Set users role\n\nIf the user instance already has a role, then the user role will be replaced with a new role. `instance.setRole(params)` params can be role `id` or `name`, and will return status in boolean.\n\n``` js\n\n   const user = await User.create({\n         name: name,\n         email: email,\n         password: hashPassword\n   })\n\n   await user.setRole(\"customer\") // params is role id or name\n\n```\n\n- ### Get role\n\nGet role object by calling `instance.getRole()`, or direcly access role name `instance.getRole().name`.\n\n``` js\n\n   user.getRole() // role object\n   user.getRole().name // role name\n\n\n```\n\n- ### Get permissions\n\nGet permission by calling `instance.getPermissions()` will get array of object, or `instance.getPermissionsName()` will get array of permissions name.\n\n``` js\n\n   user.getPermissions()     //  array of permissions object\n   user.getPermissionsName() //  array of permissions name [ \"user-create\",\"user-stored\"]\n\n```\n\n- ### Remove role\n\n``` js\n\n   user.removeRole()\n\n```\n\n- ### Check user access\n\nLimitation user access using `GateAccess(userInstance,permissionNames)`, `permissionNames` must be an array of permission names.\n\n``` js\n\n    if (!GateAccess(user, [\"user-create\",\"user-stored\",\"user-access\"]))\n        return res.sendStatus(403) // return forbidden status code\n\n```\n\n- ### Add permissions\n\n``` js\n\n   const permissions = [\n       \"user-create\",\n       \"user-stored\",\n       \"user-edit\",\n       \"user-update\",\n       \"user-delete\",\n       \"user-search\"\n   ]\n\n    for (let permission of permissions) {\n        await Permission.create({ name: permission })\n    }\n\n```\n\n- ### Add Role\n\n``` js\n\n    const roles = [ \"admin\",\"customer\" ]\n\n    for (let role of roles) {\n        await Role.create({ name: role })\n    }\n\n```\n\n- ### Assigning Permissions to Roles\n\nAssign permissions to a role by using `roleInstance.assignPermissions(params)`, params can be a list of permissions `name` or `id`.\n\n``` js\n\n    const permissions = [\n       \"user-create\",\n       \"user-stored\"\n    ]\n\n    const admin = await Role.findOne({ where: { name: \"admin\" } })\n\n    if (admin) {\n        await admin.assignPermissions(permissions)\n    }\n\n```\n\n# Resource\n\n- ### Create new resource via cli.\n\n```\n   npx nodemi make:resource UserResource\n```\n\nThe Resource will be created in `resources` directory.\n\n``` js\n\n   import Resource from \"../core/resource/Resource.js\"\n   class UserResource extends Resource {\n       constructor() {\n           super().load(this)\n       }\n\n       /**\n        * Transform the resource into custom object.\n        *\n        * @return\n        */\n       toArray(data) {\n            return {}\n       }\n    }\n\n    export default UserResource\n\n```\n\n- ### Basic usage\n\nTo create resources from a single object use `make` or `collection` for an array of objects.\n\n``` js\n\n     const userResource = new UserResource().make(user) // for single object\n\n     const userResources = new UserResource().collection(users) // for array of object\n\n```\n\n- ### Example user resource\n\n``` js\n\n   class UserResource extends Resource {\n       constructor() {\n           super().load(this)\n       }\n\n       toArray(data) {\n           return {\n                     \"id\": data.id,\n                     \"name\": data.name,\n                     \"email\": data.email,\n                     \"image\": data.getMediaByName(\"avatar\")?.url ?? '',\n                     \"role\": data.getRole()?.name ?? '',\n                     \"permissions\": new PermissionResource().collection(data.getPermissions() ?? []),\n                 }\n       }\n    }\n\n```\n\n- ### Example permissions resource\n\n``` js\n\n   class PermissionResource extends Resource {\n       constructor() {\n             super().load(this)\n         }\n\n         toArray(data) {\n             return {\n                 \"id\": data.id,\n                 \"name\": data.name\n             }\n       }\n   }\n\n```\n\n- ### Example usage\n\n``` js\n\n   const user = await User.findOne({\n       where: {\n             id: 1\n       }\n   })\n\n   const userResource = new UserResource().make(user)\n\n   res.json(userResource)\n\n```\n\n- ### Example result\n\n``` js\n\n   {\n       \"id\": 1,\n       \"name\": \"Andre\",\n       \"email\": \"andre@gmail.com\",\n       \"image\": \"http://localhost:5000/User-1/287d735a-2880-4d4f-9851-5055d1ba1aae.jpg\",\n       \"role\": \"customer\",\n       \"permissions\": [\n           {\n               \"id\": 1,\n               \"name\": \"user-create\"\n           },\n           {\n               \"id\": 2,\n               \"name\": \"user-stored\"\n           }\n       ]\n   }\n\n```\n\n# Auth Jwt\n\n- ### Create token\n\nCreate token by calling `JwtAuth.createToken()`, that will return `refreshToken` and `accessToken`.\n\n``` js\n   const payload = {\n         id: user.id,\n         name: user.name,\n         email: user.email\n     }\n\n   const token = JwtAuth.createToken(payload)\n\n   console.log(token.refreshToken)\n   console.log(token.accessToken)\n\n\n```\n\n- ### Regenerate access token\n\nRegenerate access token by calling `JwtAuth.regenerateAccessToken(refreshToken)`, that will return new access token.\n\n``` js\n\n   const accessToken = JwtAuth.regenerateAccessToken(refreshToken)\n\n```\n\n- ### Get Auth user\n\nGet authenticated user by `calling JwtAuth.getUser(req)`, that will get user by refresh token on request cookies.\n\n``` js\n\n   const user = await JwtAuth.getUser(req)\n\n```\n\nOr you just setup the .env `AUTH_GET_CURRENT_USER_ON_REQUEST=true` and you can access current authenticated user by access\n`req.user`.\n\nBefore using `JwtAuth.GetUser()`, ensure that you have set up your `User` model inside the `AuthConfig` in the `core/config/Auth.js` file. It is crucial that your User model has a `refresh_token` column, as `JwtAuth.GetUser()` will retrieve the user instance based on the `refresh_token` by default. However, if you prefer to retrieve the current authenticated user in a different manner, you can modify the `JwtAuth.GetUser()` function to suit your needs in `core/auth/JwtAuth.js` file.\n\n``` js\n   class AuthConfig {\n\n       /**\n       * Default user model for auth\n       * @returns\n       */\n       static user = User\n```\n\n- ### Use Middleware - Auth Jwt\n\nFor secure access to controller by adding `JwtAuthPass` to your router.\n\n``` js\n   import JwtAuthPass from '../core/middleware/JwtAuthPass.js';\n\n   routerAuth.use(JwtAuthPass)\n   routerAuth.get(\"/upload\", UserController.upload)\n\n   app.use(\"/api\",routerAuth)\n\n```\n\nHeader Request\n\n``` js\n    Authorization: 'Bearer ' + accessToken\n```\n\n- ### Use Middleware - Basic auth\n\nFor secure access to controller by adding `BasicAuthPass` to your router.\n\n``` js\n   import BasicAuthPass from '../core/middleware/BasicAuthPass.js';\n\n   routerAuth.use(BasicAuthPass)\n   routerAuth.get(\"/upload\", UserController.upload)\n\n   app.use(\"/api\",routerAuth)\n\n```\n\nBefore using this, make sure already set username and password for basic auth in `.env` file.\n\n``` env\n    AUTH_BASIC_AUTH_USERNAME=myBasicUsername\n    AUTH_BASIC_AUTH_PASSWORD=myBasicPassword\n```\n\nHeader Request\n\n``` js\n    Authorization: 'Basic ' + encodeBase64(myBasicUsername+':'+myBasicPassword)\n```\n\n# Locale\n\n- Config\n\nSetup locale in `core/config/Locale.js`. by default locale setup to english `en`, support locale of `english`, `indonesian`, `spanish`, `hindi`, `portuguese`, `russian`, `chinese`, `japanese`,\n\n``` js\n\n    defaultLocale: \"en\",\n    useLocale: useLocale,\n    locales: [\"en\", \"id\"]\n\n```\n\nYou can add more locale Code to `locales`. By default `locales` are only available for English `en`, and for Indonesia `id`.\n\n- Default validation error Messages\n\nAfter adding additional `locales`, it is important to update the validation error messages in the `core/locale/LangValidation.js` file, as the messages generated will depend on the selected locale.\n\n``` js\n\n   const langValidation = Object.freeze({\n       required: {\n           en: \"The _attribute_ is required\",\n           id: \"_attribute_ wajib di isi\",\n         //ja: \"_attribute_ .........\"  -\u003e adding new validation messages for code ja\n       },\n       email: {\n           en: \"The _attribute_ must in E-mail format\",\n           id: \"_attribute_ harus dalam format E-mail\",\n       },\n       match: {\n           en: \"The _attribute_ must be match with _param1_\",\n           id: \"_attribute_ harus sama dengan _param1_\"\n       },\n       ......\n\n```\n\n- Use Locale\n\nIts easy to use locale, just setup .env `LOCALE_USE=true`, then this will effect to `all` routes, so that have to has a params for locale, for the API router it should be `/api/:locale` and for the web router it should be `/:locale`.\n\n``` js\n\n   // example for api route\n   const routerAuth = express.Router()\n\n   routerAuth.use(JwtAuthPass)\n   routerAuth.get(\"/user\", UserController.getUser)\n   routerAuth.post(\"/upload\", UserController.upload)s\n\n   app.use(\"/api/:locale\", routerAuth)\n\n   // http://localhost:5000/api/en/endpoint |  http://localhost:5000/api/id/endpoint\n\n```\n\nIf you don't want to set the locale for all routes, only for a particular route, then simply set up the .env as `LOCALE_USE=false`. Then you can use the `LocalePass` middleware directly to your route.\n\n``` js\n\n   // example for web route\n   app.get(\"/:locale\",LocalePass, (req, res) =\u003e {\n\n   // http://localhost:5000/en | http://localhost:5000/id\n\n\n   // example for api route\n   app.get(\"/api/:locale/login\",LocalePass, (req, res) =\u003e {\n\n   // http://localhost:5000/api/en/login | http://localhost:5000/api/id/login\n\n```\n\n- Noted\n\nAll routers that using `LocalePass` will have the locale Code on req, accessible via `req.locale`.\n\n# Mail\n\nCreate mail via cli.\n\n```\n   npx nodemi make:mail AccountVerify\n```\n\nThe mail will be created in the `mails` directory, with `examplefile.txt` and `template.ejs`\n\n``` js\n\n   import Mail from \"../../core/mail/Mail.js\"\n\n   class AccountVerify extends Mail {\n         constructor(from = String, to = [], subject = String) {\n             super().load({\n                 from: from,\n                 to: to,\n                 subject: subject,\n                 text: \"Just need to verify that this is your email address.\",\n                 attachments: [\n                     {\n                         filename: \"theFile.txt\",\n                         path: \"mails/AccountVerify/examplefile.txt\"\n                     },\n                 ],\n                 html: {\n                     path: \"mails/AccountVerify/template.ejs\",\n                     data: {\n                         title: \"Welcome to the party!\",\n                         message: \"Just need to verify that this is your email address.\"\n                     }\n                 },\n             })\n         }\n   }\n\n   export default AccountVerify\n\n```\n\nThe `template.ejs` using express view engine `ejs` to render html into mail content.\n\n``` ejs\n   \u003c!DOCTYPE html\u003e\n     \u003chtml lang=\"en\"\u003e\n     \u003chead\u003e\n         \u003cmeta charset=\"UTF-8\"\u003e\n         \u003cmeta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\"\u003e\n         \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"\u003e\n         \u003ctitle\u003e\n             \u003c%= title %\u003e\n         \u003c/title\u003e\n     \u003c/head\u003e\n     \u003cbody\u003e\n         \u003ch3\u003eHello!\u003c/h3\u003e\n         \u003cp\u003e\n             \u003c%= message %\u003e\n         \u003c/p\u003e\n         \u003cp\u003e\n             Regards.\n         \u003c/p\u003e\n         \u003cp\u003e\n             Nodemi\n         \u003c/p\u003e\n     \u003c/body\u003e\n   \u003c/html\u003e\n\n```\n\nTo use this `template.ejs`, you need to add an `html` object with a `path` and `data` (if needed) into `super().load()` method.\n\n``` js\n   html: {\n            path: \"mails/AccountVerify/template.ejs\", // path is required\n            data: // data is optional base on your template.ejs\n            {\n                title: \"Welcome to the party!\",\n                message: \"Just need to verify that this is your email address.\"\n            }\n        }\n```\n\n- Basic usage\n\nTo send email by calling `instance.send()`\n\n``` js\n   const sendMail = new AccountVerify(\"from@gmail.com\",[\"receiver@gmail.com\"],\"Verify Account\")\n\n   await sendMail.send()\n\n```\n\n- Send file\n\nTo send files, you need to add an `attachments` to `super().load()`. See full \u003ca href=\"https://nodemailer.com/message/attachments/\"\u003edoc\u003c/a\u003e.\n\n``` js\n   attachments: [\n                     {\n                         filename: \"theFile.txt\",\n                         path: \"mails/AccountVerify/examplefile.txt\"\n                     }\n                ]\n\n```\n\n- Mail message options\n\nMessage options that you can add into `super().load()`.\n\n| Name         | Description                                                                                                                          | Type   | Required |\n| ------------ | ------------------------------------------------------------------------------------------------------------------------------------ | ------ | -------- |\n| from         | The e-mail address of the sender. All e-mail addresses can be plain 'sender@server.com'                                              | string | `Yes`    |\n| to           | Recipients e-mail addresses that will appear on the To                                                                               | array  | `Yes`    |\n| subject      | Subject of the e-mail                                                                                                                | string | No       |\n| text         | If you are using HTML for the body of the email, then this text will not be used again                                               | string | No       |\n| html         | The HTML version of the message                                                                                                      | object | No       |\n| attachments  | An array of objects is used for the purpose of sending files. See full \u003ca href=\"https://nodemailer.com/message/attachments/\"\u003edoc\u003c/a\u003e | array  | No       |\n| cc           | Recipients e-mail addresses that will appear on the Cc                                                                               | array  | No       |\n| bcc          | Recipients e-mail addresses that will appear on the Bcc                                                                              | array  | No       |\n| sender       | E-mail address that will appear on the Sender                                                                                        | string | No       |\n| replyTo      | An array of e-mail addresses that will appear on the Reply-To                                                                        | array  | No       |\n| alternatives | An array of alternative text contents. See full \u003ca href=\"https://nodemailer.com/message/alternatives/\"\u003edoc\u003c/a\u003e                       | array  | No       |\n| encoding     | optional transfer encoding for the textual parts.                                                                                    | string | No       |\n\n- Noted\n\nBefore using mail, make sure you already setup .env file\n\n``` env\n   MAIL_HOST= #example: smtp.gmail.com | smtp-relay.sendinblue.com\n   MAIL_PORT=587\n   MAIL_USERNAME=\n   MAIL_PASSWORD=\n   MAIL_FROM=\n\n```\n\n# Firebase Cloud Messaging\n\n- Send message\n\n``` js\n   const message = {\n        title: \"Notification\", // notification title\n        body: \"Hello there\",   // notification body\n        data: {\n                               // payload\n        },\n        registrationTokens: [\"token1\",\"token2\"] // target token\n   }\n\n   await FirebaseService.sendMessage(message)\n\n```\n\n- Noted\n\nBefore using FCM, make sure you already `enable` Firebase Cloud Messaging API on \u003ca href=\"https://console.cloud.google.com/\"\u003eGoogle Cloud Console\u003c/a\u003e, by selecting your project and navigating to `APIs \u0026 Services`. Once you have enabled the API, you can set up your .env\n\n``` env\n   FIREBASE_SERVICE_ACCOUNT_BASE64= # base64 of firebase service account (.json)\n   FIREBASE_CLOUD_MESSAGING_SERVER_KEY= #fcm server key\n\n```\n\n# Seeder\n\n- Running seeder\n\nRunning seeder via cli\n\n```\n    npx nodemi seed:run\n```\n\nYou can put your seeder code inside `seeder` function in the `core/seeder/Seeder.js` file\n\n``` js\n\n   const seeder = async () =\u003e {\n\n         // put code here..\n\n   }\n\n```\n\n# Cors\n\nThe configuration for Cross-Origin Resource Sharing (CORS) can be found in the `core/config/Cors.js` file.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmamena2020%2Fnodemi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmamena2020%2Fnodemi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmamena2020%2Fnodemi/lists"}