├── .github
└── workflows
│ └── node.js.yml
├── .gitignore
├── LICENSE
├── Readme.md
├── app
├── controllerMapper.js
├── controllerMapper.json
├── index.js
└── user
│ ├── controller.js
│ ├── model.js
│ └── routes.json
├── bin
└── index.js
├── config.js
├── database
├── database.js
├── index.js
├── modelMapper.js
├── modelMapper.json
└── relation.js
├── docs
├── .nojekyll
├── LICENSE
├── README.md
├── _sidebar.md
├── database.md
├── deploy.md
├── index.html
├── middleware.md
├── model.md
└── module.md
├── env.example
├── haste
├── constant
│ └── general.js
├── export
│ └── script.js
├── middleware
│ ├── middleware.js
│ ├── removeMiddleware.js
│ └── script.js
├── module
│ ├── controller
│ │ └── controller.js
│ ├── database
│ │ ├── index.js
│ │ └── model.js
│ ├── module.js
│ ├── removeModule.js
│ └── routes.json
└── utils
│ └── utils.js
├── middleware
├── isLoggedIn.js
├── middlewareMapper.js
└── middlewareMapper.json
├── package.json
├── public
└── roket.png
├── test
├── mapper.js
└── user.js
└── views
├── api-docs.ejs
├── homepage.ejs
└── includes
├── header.ejs
└── navbar.ejs
/.github/workflows/node.js.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: HasteJs CI
5 |
6 | on:
7 | push:
8 | branches: [ develop ]
9 | pull_request:
10 | branches: [ main, develop ]
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | strategy:
18 | matrix:
19 | node-version: [12.x, 14.x, 14.6.0]
20 |
21 | steps:
22 | - uses: actions/checkout@v2
23 | - name: Use Node.js ${{ matrix.node-version }}
24 | uses: actions/setup-node@v1
25 | with:
26 | node-version: ${{ matrix.node-version }}
27 | - run: npm install
28 | - run: npm test
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .idea
3 | db
4 | package-lock.json
5 | yarn-error.log
6 | yarn-lock.json
7 | .env
8 | cha-export
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 MD SULTAN MAHAMUD
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | # **[HasteJS - A nodejs framework.](http://hastejs.com/)**
2 | [](https://www.npmjs.com/package/hastejs-cli)
3 | [](https://github.com/sultanfendonus/HasteJS/blob/main/LICENSE)
4 | 
5 |
6 | HasteJS is a NodeJs framework, built on top of ExpressJS, released as free and open-source software under the MIT Licence. It is particularly designed to develop a quick Restful API in record time.
7 |
8 |
9 | **Details Docs:** http://hastejs.com
10 |
11 | [](https://app.netlify.com/sites/hastejs/deploys)
12 |
13 | **Node.js**
14 | HasteJs only requires Node.js. The current recommended version to run Haste is Node v14.
15 |
16 | **Installation and run in development mode**
17 | `npx create-haste-app my-app`
18 | `cd my-app`
19 | `npm run develop`
20 |
21 |
22 | # Module
23 |
24 | ### What is Module in HasteJs?
25 | In **HasteJs**, Modules are some smaller part of your entire project.
26 | You may want to divide your entire project into some modules so that
27 | you can easily use these modules for your other hasteJs project easily.
28 |
29 |
30 | ### Create a new module
31 | It is very easy to create a new module in your hasteJs project.
32 | Just run below comand on your project root.
33 |
34 | `npx create-module moduleName`
35 |
36 | _It will generate a new folder in app directory with controllers,
37 | model and routes._
38 |
39 | ```text
40 | .
41 | └── app
42 | └── moduleName
43 | ├── controller.js
44 | └── model.js
45 | └── routes.json
46 | ```
47 | ### Remove an existing module
48 | If you want to remove a module, just run below command on your project
49 | root -
50 |
51 | `npx remove-module moduleName`
52 |
53 | Note: Don't delete a folder or module manually from app directory.
54 | That may cause unnecessary error on your code.
55 |
56 | ### Routes
57 | Routes refer to how Rest API's endpoints respond to client requests.
58 | When you create a new module, haste by default create some REST convention endpoint for you.
59 | Here, You can add new endpoints or update old endpoints as your need.
60 |
61 | ```json
62 | {
63 | "routes": [
64 | {
65 | "method": "GET",
66 | "path": "/category",
67 | "controller": "category.find",
68 | "config": {
69 | "middleware": []
70 | }
71 | },
72 | {
73 | "method": "GET",
74 | "path": "/category/count",
75 | "controller": "category.count",
76 | "config": {
77 | "middleware": []
78 | }
79 | },
80 | {
81 | "method": "GET",
82 | "path": "/category/:id",
83 | "controller": "category.findOne",
84 | "config": {
85 | "middleware": []
86 | }
87 | },
88 | {
89 | "method": "POST",
90 | "path": "/category",
91 | "controller": "category.create",
92 | "config": {
93 | "middleware": []
94 | }
95 | },
96 | {
97 | "method": "PUT",
98 | "path": "/category/:id",
99 | "controller": "category.update",
100 | "config": {
101 | "middleware": []
102 | }
103 | },
104 | {
105 | "method": "DELETE",
106 | "path": "/category/:id",
107 | "controller": "category.delete",
108 | "config": {
109 | "middleware": []
110 | }
111 | }
112 | ]
113 | }
114 | ```
115 | ### Controller
116 | Every route passes the request to the defined controller.
117 | Controllers hold the business logic of your module.
118 | Every route must define a controller. Controllers can communicate with the model and return data to the client or Error handlers.
119 |
120 | ```javascript
121 | import {Model as Category} from '../../database/modelMapper.js'
122 |
123 | const controller = {
124 | async count(req, res, next){
125 | try {
126 | const response = await Category.count({});
127 | res.json({total: response});
128 | } catch (err) {
129 | next(err);
130 | }
131 | },
132 | }
133 | export default controller;
134 | ```
135 | The above code is responsible for the return count of the Category.
136 |
137 | If you need to add a new function to your controller, you must add it to your routes.json file with the proper structure.
138 |
139 | ### Model
140 | HasteJs uses `sequelize` for managing database operations. For
141 | updating your model you need to update your `model.js` file to structure your table.
142 | For more about sequelize model visit here: https://sequelize.org/master/manual/model-basics.html
143 |
144 | Example model:
145 | ```javascript
146 | import {sequelize} from "../../database/index.js";
147 | import DataTypes from 'sequelize';
148 |
149 | export const Model = sequelize.define('Category', {
150 | // Model attributes are defined here
151 | // This are example attributes. please change as you want.
152 | // visit https://sequelize.org/master/manual/model-basics.html for details.
153 |
154 | title: {
155 | type: DataTypes.STRING,
156 | allowNull: false
157 | },
158 | description: {
159 | type: DataTypes.STRING
160 | // allowNull defaults to true
161 | }
162 | }, {
163 | // Other model options go here
164 | });
165 | ```
166 |
167 | ### Relation/Association
168 | You can define all your relation/association here ```database/relation.js``` file.
169 |
170 | Example
171 |
172 | ```javascript
173 | import {Post, Category} from "./modelMapper.js";
174 |
175 | const relation = ()=> {
176 | Post.belongsTo(Category, {foreignKey: 'category_id'})
177 | Category.hasMany(Post, {
178 | foreignKey: 'category_id',
179 | sourceKey: 'id'
180 | })
181 | }
182 |
183 | export default relation();
184 | ```
185 |
186 | ### Default Module
187 | When you create a HasteJs project by [create-haste-app](https://www.npmjs.com/package/create-haste-app), A default User module will automatically generate for you in your app directory, So that you can focus on your main modules to develop your app in haste mode.
188 | This default user module provides the following API
189 |
190 | - Login user
191 | - Register user
192 | - Count all users
193 | - Find Me
194 | - Find all users
195 | - Find Specific user.
196 |
197 |
198 |
199 |
200 | \
201 | \
202 | **License & copyright**\
203 | © MD SULTAN MAHAMUD, Software Engineer\
204 | Licensed under the [MIT License](LICENSE).
--------------------------------------------------------------------------------
/app/controllerMapper.js:
--------------------------------------------------------------------------------
1 | import user from './user/controller.js';
2 |
3 | export const CONTROLLER_MAPPER = {
4 | user: user,
5 | }
--------------------------------------------------------------------------------
/app/controllerMapper.json:
--------------------------------------------------------------------------------
1 | {
2 | "import": [
3 | "import user from './user/controller.js';"
4 | ],
5 | "mapper": {
6 | "user": "user"
7 | }
8 | }
--------------------------------------------------------------------------------
/app/index.js:
--------------------------------------------------------------------------------
1 | import express from 'express';
2 | import cors from 'cors';
3 | import bodyParser from 'body-parser';
4 | import path from 'path'
5 | import {combinedRoutes} from "../haste/utils/utils.js";
6 | import {CONTROLLER_MAPPER} from "./controllerMapper.js";
7 | import {init} from '../database/index.js'
8 | import {MIDDLEWARE_MAPPER} from "../middleware/middlewareMapper.js";
9 | import {port} from "../config.js";
10 |
11 | import { dirname } from 'path';
12 | import { fileURLToPath } from 'url';
13 | import dotenv from 'dotenv'
14 | dotenv.config()
15 |
16 | const app = express()
17 |
18 |
19 | app.set("view engine", "ejs");
20 | const __dirname = dirname(fileURLToPath(import.meta.url));
21 | app.set("views", path.join(__dirname,"../views"));
22 |
23 |
24 | app.use(cors());
25 | app.use(bodyParser.urlencoded({ extended: false }));
26 | app.use(bodyParser.json());
27 | app.use( express.static( "public" ) );
28 |
29 | //database init
30 | init()
31 |
32 | // console.log(routes);
33 | const routes = combinedRoutes()
34 |
35 | const appendMiddlewares = (middlewares)=> {
36 | let middlewareArray = []
37 | middlewares.forEach((item)=> {
38 | middlewareArray.push(MIDDLEWARE_MAPPER[item])
39 | })
40 | return middlewareArray;
41 | }
42 |
43 | routes.map((item)=> {
44 | const [controller, method] = item.controller.split('.');
45 | const middlewares = item.config['middleware']
46 | app[item.method.toLowerCase()](item.path, middlewares.length > 0 ? appendMiddlewares(middlewares): [],
47 | CONTROLLER_MAPPER[controller][method])
48 | })
49 |
50 | let debugMode = process.env.DEBUG_MODE || true;
51 |
52 | if(debugMode === true || debugMode === 'true'){
53 | app.get("/", (req, res) => {
54 | res.render("homepage",{
55 | pageTitle: "HasteJs - Homepage",
56 | version: process.env.npm_package_version
57 | });
58 | });
59 |
60 | app.get("/api-docs", (req, res) => {
61 | res.render("api-docs",{
62 | pageTitle: "HasteJs - API Documentation",
63 | version: process.env.npm_package_version,
64 | api: routes,
65 | port: process.env.PORT_NUMBER || port
66 | });
67 | });
68 | }
69 |
70 |
71 | app.use((err, req, res, next) => {
72 | if (!err.statusCode) {
73 | err.statusCode = 500;
74 | }
75 | res.status(err.statusCode).send({ code: err.statusCode, error: err.message });
76 | });
77 |
78 |
79 | export const server = app.listen(process.env.PORT_NUMBER || port, () => {
80 | console.log(`Haste app listening at http://localhost:${process.env.PORT_NUMBER || port}`)
81 | console.log(`API docs at http://localhost:${process.env.PORT_NUMBER || port}/api-docs`)
82 | console.log(`Full HasteJs documentation at http://hastejs.com`)
83 | })
--------------------------------------------------------------------------------
/app/user/controller.js:
--------------------------------------------------------------------------------
1 | import {Model as User} from './model.js';
2 | import bcrypt from 'bcryptjs';
3 | import jwt from 'jsonwebtoken';
4 | import {jwtSecretKey} from "../../config.js";
5 |
6 | const controller = {
7 | async find(req, res, next){
8 | try {
9 | const response = await User.scope("withoutPassword").findAll({});
10 | res.send(response);
11 | } catch (err) {
12 | next(err);
13 | }
14 | },
15 | async count(req, res, next){
16 | try {
17 | const response = await User.count({});
18 | res.json({total: response});
19 | } catch (err) {
20 | next(err);
21 | }
22 | },
23 | async findOne(req, res, next){
24 | try {
25 | const response = await User.scope("withoutPassword").findOne({
26 | where: {
27 | id: req.params.id
28 | }
29 | });
30 | if(response){
31 | res.send(response);
32 | }else {
33 | res.status(404).json({message: 'No item found!'})
34 | }
35 | } catch (err) {
36 | next(err);
37 | }
38 | },
39 | async findMe(req, res, next){
40 | try {
41 | const response = await User.scope("withoutPassword").findOne({
42 | where: {
43 | id: req.user_id
44 | }
45 | });
46 | if(response){
47 | res.send(response);
48 | }else {
49 | res.status(404).json({message: 'No item found!'})
50 | }
51 | } catch (err) {
52 | next(err);
53 | }
54 | },
55 | async create(req, res, next){
56 | try {
57 | bcrypt.genSalt(10, function (err, salt) {
58 | bcrypt.hash(req.body.password, salt, async function (err, hash) {
59 | //data insert
60 | let response;
61 | try {
62 | response = await User.create({
63 | first_name: req.body.first_name,
64 | last_name: req.body.last_name,
65 | email: req.body.email,
66 | password: hash,
67 | });
68 | } catch (e) {
69 | next(e);
70 | }
71 |
72 | const token = jwt.sign(
73 | {
74 | email: response.email,
75 | user_id: response.id
76 | },
77 | process.env.JWT_SECRET || jwtSecretKey
78 | );
79 |
80 | res.status(201).json({
81 | email: req.body.email,
82 | first_name: req.body.first_name,
83 | last_name: req.body.last_name,
84 | token: token
85 | });
86 | });
87 | });
88 | } catch (err) {
89 | next(err);
90 | }
91 | },
92 | async login(req, res, next){
93 | try{
94 | const user = await User.findOne({
95 | where: { email: req.body.email }
96 | });
97 | if (user) {
98 | bcrypt.compare(req.body.password, user.password, async function (err, result) {
99 | // res === true
100 | if (result === true) {
101 | const token = jwt.sign(
102 | {
103 | email: user.email,
104 | user_id: user.id
105 | },
106 | process.env.JWT_SECRET || jwtSecretKey
107 | );
108 |
109 | const userData = await User.scope('withoutPassword').findOne({
110 | where: { email: req.body.email }
111 | });
112 |
113 | res.status(200).json({
114 | user: userData,
115 | token: token
116 | });
117 | } else {
118 | res
119 | .status(401)
120 | .json({ errors: [{ msg: 'Invalid email or password!' }] });
121 | }
122 | });
123 | } else {
124 | res.status(401).json({ errors: [{ msg: 'Invalid email or password!' }] });
125 | }
126 | }catch (err){
127 | next(err)
128 | }
129 | }
130 | }
131 | export default controller;
--------------------------------------------------------------------------------
/app/user/model.js:
--------------------------------------------------------------------------------
1 | import {sequelize} from "../../database/index.js";
2 | import DataTypes from 'sequelize';
3 |
4 | export const Model = sequelize.define('User', {
5 | // Model attributes are defined here
6 | // This are example attributes. please change as you want.
7 | // visit https://sequelize.org/master/manual/model-basics.html for details.
8 |
9 | first_name: {
10 | type: DataTypes.STRING,
11 | allowNull: false
12 | },
13 | last_name: {
14 | type: DataTypes.STRING
15 | },
16 | email: {
17 | type: DataTypes.STRING,
18 | allowNull: false,
19 | unique: true
20 | },
21 | password: {
22 | type: DataTypes.STRING
23 | },
24 | isActive: {
25 | type: DataTypes.BOOLEAN,
26 | defaultValue: true
27 | },
28 | isConfirmed: {
29 | type: DataTypes.BOOLEAN,
30 | defaultValue: false
31 | }
32 | }, {
33 | // Other model options go here
34 | defaultScope: {
35 | attributes: { exclude: [''] },
36 | },
37 | scopes: {
38 | withoutPassword: {
39 | attributes: {exclude: ['password']},
40 | }
41 | }
42 | });
--------------------------------------------------------------------------------
/app/user/routes.json:
--------------------------------------------------------------------------------
1 | {
2 | "routes": [
3 | {
4 | "method": "GET",
5 | "path": "/user",
6 | "controller": "user.find",
7 | "config": {
8 | "middleware": ["isLoggedIn"]
9 | }
10 | },
11 | {
12 | "method": "GET",
13 | "path": "/user/count",
14 | "controller": "user.count",
15 | "config": {
16 | "middleware": ["isLoggedIn"]
17 | }
18 | },
19 | {
20 | "method": "GET",
21 | "path": "/user/:id",
22 | "controller": "user.findOne",
23 | "config": {
24 | "middleware": ["isLoggedIn"]
25 | }
26 | },
27 | {
28 | "method": "GET",
29 | "path": "/user/get/me",
30 | "controller": "user.findMe",
31 | "config": {
32 | "middleware": ["isLoggedIn"]
33 | }
34 | },
35 | {
36 | "method": "POST",
37 | "path": "/user/register",
38 | "controller": "user.create",
39 | "config": {
40 | "middleware": []
41 | }
42 | },
43 | {
44 | "method": "POST",
45 | "path": "/user/login",
46 | "controller": "user.login",
47 | "config": {
48 | "middleware": []
49 | }
50 | }
51 | ]
52 | }
--------------------------------------------------------------------------------
/bin/index.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | console.log("hello from hastejs-cli");
--------------------------------------------------------------------------------
/config.js:
--------------------------------------------------------------------------------
1 | export const port = 4999;
2 | export const autoMigration = true; //false to off auto migration/syncing.
3 | export const jwtSecretKey = 'secretssh';
4 | export const db_log = true;
--------------------------------------------------------------------------------
/database/database.js:
--------------------------------------------------------------------------------
1 | import dotenv from 'dotenv'
2 | dotenv.config()
3 |
4 | export const dbConfig = {
5 | settings: {
6 | client: process.env.DB_CLIENT || 'sqlite', /* one of 'sqlite' | 'mysql' | 'mariadb' | 'postgres' | 'mssql' */
7 | host: process.env.DB_HOSTNAME || '127.0.0.1',
8 | port: process.env.DB_PORT || '5432',
9 | database: process.env.DB_NAME || 'haste',
10 | username: process.env.DB_USER || 'haste',
11 | password: process.env.DB_PASSWORD || '',
12 | }
13 | }
--------------------------------------------------------------------------------
/database/index.js:
--------------------------------------------------------------------------------
1 | import Sequelize from 'sequelize';
2 | import {autoMigration} from '../config.js'
3 | import {dbConfig} from "./database.js";
4 | import dotenv from 'dotenv'
5 | dotenv.config()
6 |
7 | export let sequelize;
8 |
9 | if(dbConfig.settings.client === 'sqlite'){
10 | sequelize= new Sequelize({
11 | dialect: 'sqlite',
12 | storage: 'db/database.sqlite',
13 | logging: (msg)=> {
14 | if(!process.env.DB_LOGS || process.env.DB_LOGS === 'false'){
15 | return false;
16 | }else {
17 | return console.log(msg)
18 | }
19 | }
20 | });
21 | }else {
22 | const {client, host, port, database, username, password} = dbConfig.settings;
23 | sequelize = new Sequelize(database, username, password, {
24 | host: `${host}`,
25 | dialect: client,
26 | logging: (msg)=> {
27 | if(!process.env.DB_LOGS || process.env.DB_LOGS === 'false'){
28 | return false;
29 | }else {
30 | return console.log(msg)
31 | }
32 | }
33 | });
34 | }
35 |
36 | export const syncAll = async ()=> {
37 | await sequelize.sync({ alter: true });
38 | }
39 |
40 | export const init = async ()=> {
41 | try {
42 | await sequelize.authenticate();
43 | console.log('Connection with database has been established successfully.');
44 | autoMigration && await syncAll()
45 | } catch (error) {
46 | console.error('Unable to connect to the database:', error);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/database/modelMapper.js:
--------------------------------------------------------------------------------
1 | import relation from './relation.js';
2 |
3 | export {}
--------------------------------------------------------------------------------
/database/modelMapper.json:
--------------------------------------------------------------------------------
1 | {
2 | "import": [
3 | "import relation from './relation.js';"
4 | ],
5 | "export": []
6 | }
--------------------------------------------------------------------------------
/database/relation.js:
--------------------------------------------------------------------------------
1 | // import your models here..
2 |
3 | const relation = ()=> {
4 | // Write your association/Relation logic here...
5 | }
6 |
7 | export default relation();
--------------------------------------------------------------------------------
/docs/.nojekyll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sultanfendonus/HasteJS/5e32d348c706696192506eea61536517bc8d8eec/docs/.nojekyll
--------------------------------------------------------------------------------
/docs/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 MD SULTAN MAHAMUD
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # **HasteJS - A nodejs framework.**
2 |
3 | HasteJS is a NodeJs framework, built on top of ExpressJS, released as free and open-source software under the MIT Licence. It is particularly designed to develop a quick Restful API in record time.
4 |
5 |
6 | **Node.js**\
7 | HasteJs only requires Node.js. The current recommended version to run Haste is Node v14.
8 |
9 | **Installation and run in development mode**\
10 | `npx create-haste-app my-app`\
11 | `cd my-app`\
12 | `npm run develop`
13 |
14 | Now create a new [module](module.md). [Click here](module.md)
15 |
16 |
17 |
18 | \
19 | \
20 | **License & copyright**\
21 | © MD SULTAN MAHAMUD, Software Engineer\
22 | Licensed under the [MIT License](LICENSE).
--------------------------------------------------------------------------------
/docs/_sidebar.md:
--------------------------------------------------------------------------------
1 | * [Home](README.md "HasteJS - A nodejs framework")
2 | * [Module](module.md "HasteJs Module")
3 | * [Introduction](module.md?id=module)
4 | * [Create a new module](module.md?id=create-a-new-module)
5 | * [Remove an existing module](module.md?id=remove-an-existing-module)
6 | * [Routes](module.md?id=routes)
7 | * [Controllers](module.md?id=controller)
8 | * [Model](module.md?id=model)
9 | * [Relation/Association](module.md?id=relationassociation)
10 | * [Default Module](module.md?id=default-module)
11 | * [MiddleWare](middleware.md)
12 | * [What is middleware?](middleware.md?id=what-is-middleware)
13 | * [Create a new middleware](middleware.md?id=create-a-new-middleware)
14 | * [Remove an existing middleware](middleware.md?id=remove-an-existing-middleware)
15 | * [Database](database.md)
16 | * [Deployment](deploy.md)
--------------------------------------------------------------------------------
/docs/database.md:
--------------------------------------------------------------------------------
1 | # Database
2 | HasteJs currently supports `MySQL`, `PostgreSQL`, `SQLite` and `MS SQL`.
3 |
4 | `MongoDB will be added soon.`
5 |
6 | HasteJs uses SQLite as a default database. But, you can
7 | use your appropriate one by changing
8 | your `.env` file.
9 |
10 | First, Create a `.env` file on your project root and copy the
11 | whole variable list from `env.example`
12 | file to your newly created `.env` file.
13 |
14 | Now change the following fields as your database reference value -
15 |
16 | ```dotenv
17 | DB_CLIENT = sqlite
18 | DB_HOSTNAME =
19 | DB_PORT =
20 | DB_NAME =
21 | DB_USER =
22 | DB_PASSWORD =
23 | ```
24 | You need to restart the project. Stop the project and start again by
25 | `npm run develop`
26 |
27 |
28 | ### Auto Migration
29 | HasteJs and sequelize automatically manage your database migration
30 | when you changed your model files. So you don't have to worry
31 | about database migration.
32 |
33 | ### Manual Migration
34 | If you want to migrate your data manually then you have to follow
35 | 2 steps -
36 | 1. Disable auto migration from `Project Root -> config.js`.
37 | ```
38 | export const autoMigration = false;
39 | ```
40 | 2. Follow this documentation https://sequelize.org/master/manual/migrations.html
--------------------------------------------------------------------------------
/docs/deploy.md:
--------------------------------------------------------------------------------
1 | # Deployment
2 | You can easily deploy a hasteJs project as you deploy a normal nodejs project.
3 |
4 | Here is a basic example of deployment -
5 |
6 | 1. Login to your server, and install [nodejs](https://nodejs.org/en/).
7 |
8 |
9 | 2. Install pm2 globally \
10 | `npm install pm2 -g`
11 |
12 |
13 | 3. Clone your repository and go to project root.
14 |
15 |
16 | 4. Run `npm install` for node dependency.
17 |
18 |
19 | 5. Create a `.env` file and copy the `env.example` file content to the .env file.
20 |
21 |
22 | 6. Change all nessasary changes like port number to 80, set database credential etc.
23 | and save the file.
24 |
25 |
26 |
27 | 7. Run `npm run deploy` for deployment.
28 |
29 |
30 | 8. If you want to stop a hasteJs application just run `pm2 list` for a list of applications running
31 | and run `pm2 stop id` to stop an application. For more info about pm2 visit https://pm2.keymetrics.io/
32 |
33 |
34 | 9. If you make any changes to the application code base then you must restart
35 | the process in pm2. Run `pm2 restart id` for the restart of application.
36 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | HasteJS - A nodejs framework
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/docs/middleware.md:
--------------------------------------------------------------------------------
1 | # Middleware
2 |
3 | ### What is middleware?
4 | Middleware is a piece of code or functions that have access to the
5 | request object (req), the response object (res), and the next middleware
6 | function in the application’s request-response cycle. The next middleware
7 | function is commonly denoted by a variable named next.
8 | More about middleware visit [ExpressJS Middleware](https://expressjs.com/en/guide/using-middleware.html)
9 |
10 | In a general sense, you will need a middleware when
11 | you want to verify the request and do some stuff with the request
12 | before the controller function execute.
13 | You can define one or multiple middlewares in every route.
14 |
15 | `create-haste-app` automatically generates "isLoggedIn"
16 | middleware for you in `app -> middleware` folder.
17 | This middleware verifies the JW token and passes the request
18 | to the controller or error handlers. You can use this middleware
19 | on any of your routes to secure the route.
20 |
21 | Here user count route is secured by `isLoggedIn` middleware. You can add multiple
22 | middleware in middleware array.
23 |
24 | ```json
25 | {
26 | "method": "GET",
27 | "path": "/user/count",
28 | "controller": "user.count",
29 | "config": {
30 | "middleware": ["isLoggedIn"]
31 | }
32 | }
33 | ```
34 |
35 | You can generate a new middleware for your need. Here is the shortcut way-
36 |
37 | ### Create a new middleware
38 | `npx create-middleware middlewareName`
39 |
40 | _It will generate a new middleware file for you._
41 |
42 | ```text
43 | .
44 | └── middleware
45 | ├── isLoggedIn.js
46 | └── middlewareName.js
47 | ```
48 | ```javascript
49 | const middlewareName = (req,res,next) => {
50 | try{
51 | next();
52 | }catch (e) {
53 | next(error);
54 | }
55 | }
56 |
57 | export default middlewareName;
58 | ```
59 |
60 | ### Remove an existing middleware
61 | If you want to remove a middleware, just run below command on your project
62 | root -
63 |
64 | `npx remove-middleware middlewareName`
65 |
66 | Note: Don't delete middleware files manually from `middleware` directory.
67 | That may cause unnecessary error on your code.
--------------------------------------------------------------------------------
/docs/model.md:
--------------------------------------------------------------------------------
1 | **Update the model**\
2 | HasteJs uses `sequelize` for managing database operations. now you need to
3 | update your `model.js` file to structure your table.
4 | For more about sequelize model visit here: https://sequelize.org/master/manual/model-basics.html
5 |
6 |
--------------------------------------------------------------------------------
/docs/module.md:
--------------------------------------------------------------------------------
1 | # Module
2 |
3 | ### What is Module in HasteJs?
4 | In **HasteJs**, Modules are some smaller part of your entire project.
5 | You may want to divide your entire project into some modules so that
6 | you can easily use these modules for your other hasteJs project easily.
7 |
8 |
9 | ### Create a new module
10 | It is very easy to create a new module in your hasteJs project.
11 | Just run below comand on your project root.
12 |
13 | `npx create-module moduleName`
14 |
15 | _It will generate a new folder in app directory with controllers,
16 | model and routes._
17 |
18 | ```text
19 | .
20 | └── app
21 | └── moduleName
22 | ├── controller.js
23 | └── model.js
24 | └── routes.json
25 | ```
26 | ### Remove an existing module
27 | If you want to remove a module, just run below command on your project
28 | root -
29 |
30 | `npx remove-module moduleName`
31 |
32 | Note: Don't delete a folder or module manually from app directory.
33 | That may cause unnecessary error on your code.
34 |
35 | ### Routes
36 | Routes refer to how Rest API's endpoints respond to client requests.
37 | When you create a new module, haste by default create some REST convention endpoint for you.
38 | Here, You can add new endpoints or update old endpoints as your need.
39 |
40 | ```json
41 | {
42 | "routes": [
43 | {
44 | "method": "GET",
45 | "path": "/category",
46 | "controller": "category.find",
47 | "config": {
48 | "middleware": []
49 | }
50 | },
51 | {
52 | "method": "GET",
53 | "path": "/category/count",
54 | "controller": "category.count",
55 | "config": {
56 | "middleware": []
57 | }
58 | },
59 | {
60 | "method": "GET",
61 | "path": "/category/:id",
62 | "controller": "category.findOne",
63 | "config": {
64 | "middleware": []
65 | }
66 | },
67 | {
68 | "method": "POST",
69 | "path": "/category",
70 | "controller": "category.create",
71 | "config": {
72 | "middleware": []
73 | }
74 | },
75 | {
76 | "method": "PUT",
77 | "path": "/category/:id",
78 | "controller": "category.update",
79 | "config": {
80 | "middleware": []
81 | }
82 | },
83 | {
84 | "method": "DELETE",
85 | "path": "/category/:id",
86 | "controller": "category.delete",
87 | "config": {
88 | "middleware": []
89 | }
90 | }
91 | ]
92 | }
93 | ```
94 | ### Controller
95 | Every route passes the request to the defined controller.
96 | Controllers hold the business logic of your module.
97 | Every route must define a controller. Controllers can communicate with the model and return data to the client or Error handlers.
98 |
99 | ```javascript
100 | import {Model as Category} from '../../database/modelMapper.js'
101 |
102 | const controller = {
103 | async count(req, res, next){
104 | try {
105 | const response = await Category.count({});
106 | res.json({total: response});
107 | } catch (err) {
108 | next(err);
109 | }
110 | },
111 | }
112 | export default controller;
113 | ```
114 | The above code is responsible for the return count of the Category.
115 |
116 | If you need to add a new function to your controller, you must add it to your routes.json file with the proper structure.
117 |
118 | ### Model
119 | HasteJs uses `sequelize` for managing database operations. For
120 | updating your model you need to update your `model.js` file to structure your table.
121 | For more about sequelize model visit here: https://sequelize.org/master/manual/model-basics.html
122 |
123 | Example model:
124 | ```javascript
125 | import {sequelize} from "../../database/index.js";
126 | import DataTypes from 'sequelize';
127 |
128 | export const Model = sequelize.define('Category', {
129 | // Model attributes are defined here
130 | // This are example attributes. please change as you want.
131 | // visit https://sequelize.org/master/manual/model-basics.html for details.
132 |
133 | title: {
134 | type: DataTypes.STRING,
135 | allowNull: false
136 | },
137 | description: {
138 | type: DataTypes.STRING
139 | // allowNull defaults to true
140 | }
141 | }, {
142 | // Other model options go here
143 | });
144 | ```
145 |
146 | ### Relation/Association
147 | You can define all your relation/association here ```database/relation.js``` file.
148 |
149 | Example
150 |
151 | ```javascript
152 | import {Post, Category} from "./modelMapper.js";
153 |
154 | const relation = ()=> {
155 | Post.belongsTo(Category, {foreignKey: 'category_id'})
156 | Category.hasMany(Post, {
157 | foreignKey: 'category_id',
158 | sourceKey: 'id'
159 | })
160 | }
161 |
162 | export default relation();
163 | ```
164 |
165 | ### Default Module
166 | When you create a HasteJs project by [create-haste-app](https://www.npmjs.com/package/create-haste-app), A default User module will automatically generate for you in your app directory, So that you can focus on your main modules to develop your app in haste mode.
167 | This default user module provides the following API
168 |
169 | - Login user
170 | - Register user
171 | - Count all users
172 | - Find Me
173 | - Find all users
174 | - Find Specific user.
175 |
176 |
177 |
--------------------------------------------------------------------------------
/env.example:
--------------------------------------------------------------------------------
1 | PORT_NUMBER = 4999
2 | JWT_SECRET = secretssh
3 | DEBUG_MODE = true
4 |
5 | #Database 'sqlite' | 'mysql' | 'mariadb' | 'postgres' | 'mssql'
6 | DB_LOGS = false
7 | DB_CLIENT = sqlite
8 | DB_HOSTNAME =
9 | DB_PORT =
10 | DB_NAME =
11 | DB_USER =
12 | DB_PASSWORD =
--------------------------------------------------------------------------------
/haste/constant/general.js:
--------------------------------------------------------------------------------
1 | export const frameworkName = "haste"
--------------------------------------------------------------------------------
/haste/export/script.js:
--------------------------------------------------------------------------------
1 | import fse from 'fs-extra'
2 | import path from "path";
3 | import fs from "fs";
4 |
5 | const exportedFolder = [
6 | "app",
7 | "database",
8 | "haste",
9 | "middleware",
10 | "public",
11 | "test",
12 | "views",
13 | ".gitignore",
14 | "config.js",
15 | "env.example",
16 | "package.json",
17 | "Readme.md"
18 | ]
19 |
20 | const currentDir = process.cwd();
21 |
22 | exportedFolder.forEach((item, index)=> {
23 | const sourceDir = path.join(currentDir, item);
24 | const destinationDir = path.join(currentDir, `cha-export/${item}`);
25 |
26 | fse.copy(sourceDir, destinationDir, (err)=> {
27 | if (err){
28 | console.log(err)
29 | }else {
30 | if(index === exportedFolder.length - 1){
31 | const packageJonFilePath = path.join(currentDir, 'cha-export/package.json');
32 | const packageJsonContent = fs.readFileSync(packageJonFilePath);
33 | let packageJson = JSON.parse(packageJsonContent.toString());
34 | packageJson.dependencies['hastejs-cli'] = packageJson.version;
35 | packageJson.name = "packageName";
36 | delete packageJson.description;
37 | delete packageJson.repository;
38 | delete packageJson.bin;
39 | delete packageJson.author;
40 | delete packageJson.license;
41 |
42 | fs.writeFileSync(packageJonFilePath, JSON.stringify(packageJson, null, 2));
43 | }
44 |
45 | }
46 | })
47 | })
48 |
49 |
--------------------------------------------------------------------------------
/haste/middleware/middleware.js:
--------------------------------------------------------------------------------
1 | const MIDDLEWARE_NAME = (req,res,next) => {
2 | // Your middleware code write here...
3 |
4 | next()
5 | }
6 |
7 | export default MIDDLEWARE_NAME;
--------------------------------------------------------------------------------
/haste/middleware/removeMiddleware.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import {frameworkName} from "../constant/general.js";
4 | import shell from "shelljs";
5 | import fs from "fs";
6 | import chalk from "chalk";
7 | import clear from "clear";
8 | import figlet from "figlet";
9 |
10 | //Little style
11 | clear();
12 | console.log(
13 | chalk.yellow(
14 | figlet.textSync(frameworkName, { horizontalLayout: 'full' })
15 | )
16 | );
17 |
18 | // Get middleware name
19 | let middlewareName = process.argv[2];
20 | middlewareName = middlewareName.toLowerCase();
21 |
22 | // isLoggedIn Middleware can not be deleted
23 | if(middlewareName === 'isLoggedIn'){
24 | console.log(chalk.red('isLoggedIn Middleware can not be removed!'));
25 | process.exit(1);
26 | }
27 |
28 | //Check a Module Exists or Not
29 | let dir = `./middleware/${middlewareName}.js`;
30 | if(!fs.existsSync(dir)){
31 | console.log(chalk.red('Middleware Not Found!'))
32 | process.exit(1)
33 | }
34 |
35 | const removeMiddleWareFile = ()=> {
36 | shell.rm('-rf', dir)
37 | }
38 | removeMiddleWareFile();
39 |
40 |
41 | const removeMiddlewareMapper = ()=> {
42 | const file_content = fs.readFileSync('./middleware/middlewareMapper.json');
43 |
44 | let middlewareMapper = JSON.parse(file_content.toString());
45 |
46 | // middlewareMapper.import.push(`import ${middlewareName} from `+"'"+`./${middlewareName}.js` +"';")
47 | // middlewareMapper.mapper[middlewareName] = middlewareName
48 |
49 | const index = middlewareMapper.import.indexOf(`import ${middlewareName} from `+"'"+`./${middlewareName}.js` +"';");
50 | if (index > -1) {
51 | middlewareMapper.import.splice(index, 1);
52 | }
53 |
54 | delete middlewareMapper.mapper[middlewareName]
55 |
56 | let middlewareMapperText = "";
57 |
58 | middlewareMapper.import.forEach((item)=> {
59 | middlewareMapperText = middlewareMapperText.concat(`${item} \n`)
60 | })
61 | middlewareMapperText = middlewareMapperText.concat(`\n`)
62 |
63 | let mapperObject = ""
64 | Object.keys(middlewareMapper.mapper).forEach((item)=> {
65 | mapperObject = mapperObject.concat(` ${item}: ${item}, \n`)
66 | })
67 |
68 | let mapperObjectText = "export const MIDDLEWARE_MAPPER = {\n" +
69 | mapperObject +
70 | "}"
71 | middlewareMapperText = middlewareMapperText.concat(mapperObjectText)
72 |
73 | fs.writeFileSync('./middleware/middlewareMapper.js', middlewareMapperText)
74 | fs.writeFileSync('./middleware/middlewareMapper.json', JSON.stringify(middlewareMapper, null, 4))
75 | console.log(chalk.green("Middleware Removed Successfully!"));
76 | }
77 |
78 | removeMiddlewareMapper();
--------------------------------------------------------------------------------
/haste/middleware/script.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import {frameworkName} from "../constant/general.js";
4 | import {capitalizeFirstLetter, copyFile, isDuplicate} from "../utils/utils.js";
5 | import shell from "shelljs";
6 | import fs from "fs";
7 | import clear from "clear";
8 | import chalk from "chalk";
9 | import figlet from "figlet";
10 |
11 |
12 | //Little style
13 | clear();
14 | console.log(
15 | chalk.yellow(
16 | figlet.textSync(frameworkName, { horizontalLayout: 'full' })
17 | )
18 | );
19 |
20 | // Get middleware name
21 | let middlewareName = process.argv[2];
22 | middlewareName = middlewareName.toLowerCase();
23 |
24 | const copyAndReplaceMiddlewareFile = ()=> {
25 | //copy controller.js file
26 | const sourceDir = `./${frameworkName}/middleware/middleware.js`;
27 | const destinationDir = `./middleware/${middlewareName}.js`;
28 | copyFile(sourceDir, destinationDir, 'Middleware File');
29 |
30 | //replace with module name
31 | shell.sed('-i', 'MIDDLEWARE_NAME', middlewareName, destinationDir);
32 | }
33 |
34 | const generateMiddlewareMapper = ()=> {
35 | const file_content = fs.readFileSync('./middleware/middlewareMapper.json');
36 |
37 | let middlewareMapper = JSON.parse(file_content.toString());
38 |
39 | middlewareMapper.import.push(`import ${middlewareName} from `+"'"+`./${middlewareName}.js` +"';")
40 | middlewareMapper.mapper[middlewareName] = middlewareName
41 |
42 | let middlewareMapperText = "";
43 |
44 | middlewareMapper.import.forEach((item)=> {
45 | middlewareMapperText = middlewareMapperText.concat(`${item} \n`)
46 | })
47 | middlewareMapperText = middlewareMapperText.concat(`\n`)
48 |
49 | let mapperObject = ""
50 | Object.keys(middlewareMapper.mapper).forEach((item)=> {
51 | mapperObject = mapperObject.concat(` ${item}: ${item}, \n`)
52 | })
53 |
54 | let mapperObjectText = "export const MIDDLEWARE_MAPPER = {\n" +
55 | mapperObject +
56 | "}"
57 | middlewareMapperText = middlewareMapperText.concat(mapperObjectText)
58 |
59 | fs.writeFileSync('./middleware/middlewareMapper.js', middlewareMapperText)
60 | fs.writeFileSync('./middleware/middlewareMapper.json', JSON.stringify(middlewareMapper, null, 4))
61 | }
62 |
63 | copyAndReplaceMiddlewareFile();
64 | generateMiddlewareMapper();
--------------------------------------------------------------------------------
/haste/module/controller/controller.js:
--------------------------------------------------------------------------------
1 | import {UPPER} from '../../database/modelMapper.js'
2 |
3 | const controller = {
4 | async find(req, res, next){
5 | try {
6 | let { limit, page } = req.query;
7 | if (!page || page < 0) {
8 | page = 0;
9 | }else {
10 | page = page - 1;
11 | }
12 | if (!limit || limit < 0) {
13 | limit = 10;
14 | }
15 | const response = await UPPER.findAll({
16 | limit: limit,
17 | offset: page * limit
18 | });
19 |
20 | const total = await UPPER.count();
21 | let totalPages = total / limit;
22 | totalPages = Math.ceil(totalPages);
23 | res.status(200).json({ totalItems: total, totalPages: totalPages, contents: response });
24 | } catch (err) {
25 | next(err);
26 | }
27 | },
28 | async count(req, res, next){
29 | try {
30 | const response = await UPPER.count({});
31 | res.json({total: response});
32 | } catch (err) {
33 | next(err);
34 | }
35 | },
36 | async findOne(req, res, next){
37 | try {
38 | const response = await UPPER.findOne({
39 | where: {
40 | id: req.params.id
41 | }
42 | });
43 | if(response){
44 | res.send(response);
45 | }else {
46 | res.status(404).json({message: 'No item found!'})
47 | }
48 | } catch (err) {
49 | next(err);
50 | }
51 | },
52 | async create(req, res, next){
53 | try {
54 | const response = await UPPER.create(req.body);
55 | res.status(201).json(response);
56 | } catch (err) {
57 | next(err);
58 | }
59 | },
60 | async update(req, res, next){
61 | try {
62 | const response = await UPPER.update(req.body, {
63 | where: req.params
64 | });
65 | if(response[0] === 1){
66 | res.status(200).json({status: 'success', message: 'Item Updated!'});
67 | }else {
68 | res.status(400).json({message: 'Something went wrong when update the data!'})
69 | }
70 |
71 | } catch (err) {
72 | next(err);
73 | }
74 | },
75 | async delete(req, res, next){
76 | try {
77 | const response = await UPPER.destroy({
78 | where: req.params
79 | })
80 | if(response === 1){
81 | res.status(200).json({status: 'success', message: 'Item Deleted Successfully!'})
82 | }else {
83 | res.status(404).json({message: 'Item not found!'})
84 | }
85 |
86 | } catch (err) {
87 | next(err);
88 | }
89 | }
90 | }
91 | export default controller;
--------------------------------------------------------------------------------
/haste/module/database/index.js:
--------------------------------------------------------------------------------
1 | import shell from 'shelljs';
2 | import {copyFile} from "../../utils/utils.js";
3 | import {frameworkName} from "../../constant/general.js";
4 |
5 | const initSequelize = ()=> {
6 | }
7 |
8 | initSequelize();
--------------------------------------------------------------------------------
/haste/module/database/model.js:
--------------------------------------------------------------------------------
1 | import {sequelize} from "../../database/index.js";
2 | import DataTypes from 'sequelize';
3 |
4 | export const Model = sequelize.define('REPLACE_ME', {
5 | // Model attributes are defined here
6 | // This are example attributes. please change as you want.
7 | // visit https://sequelize.org/master/manual/model-basics.html for details.
8 |
9 | title: {
10 | type: DataTypes.STRING,
11 | allowNull: false
12 | },
13 | description: {
14 | type: DataTypes.STRING
15 | // allowNull defaults to true
16 | }
17 | }, {
18 | // Other model options go here
19 | });
20 |
--------------------------------------------------------------------------------
/haste/module/module.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import fs from 'fs';
4 | import shell from 'shelljs'
5 | import clear from 'clear'
6 | import chalk from "chalk"
7 | import figlet from 'figlet'
8 | import {frameworkName} from "../constant/general.js";
9 | import {copyFile, isDuplicate} from "../utils/utils.js";
10 | import {capitalizeFirstLetter} from "../utils/utils.js";
11 |
12 | //Little style
13 | clear();
14 | console.log(
15 | chalk.yellow(
16 | figlet.textSync(frameworkName, { horizontalLayout: 'full' })
17 | )
18 | );
19 |
20 | // Get module name
21 | let moduleName = process.argv[2];
22 | moduleName = moduleName.toLowerCase();
23 |
24 | //CREATE A NEW DIRECTORY DEFINE FROM ARGS.
25 | let dir = `./app/${moduleName}`;
26 | if(!fs.existsSync(dir)){
27 | fs.mkdirSync(dir);
28 | }else {
29 | console.log(chalk.red('Module already exist! Please create a new module or update existing one from app directory.'))
30 | process.exit(1)
31 | }
32 |
33 | const copyAndReplaceRouteFile = ()=> {
34 | //copy routes.json file
35 | const sourceDir = `./${frameworkName}/module/routes.json`;
36 | const destinationDir = `./app/${moduleName}/routes.json`;
37 | copyFile(sourceDir, destinationDir, 'Routes File');
38 |
39 | //replace with module name
40 | shell.sed('-i', 'REPLACE_ME', moduleName, destinationDir);
41 | }
42 |
43 | copyAndReplaceRouteFile();
44 |
45 | const copyAndReplaceControllerFile = ()=> {
46 | //copy controller.js file
47 | const controllerSourceDir = `./${frameworkName}/module/controller/controller.js`;
48 | const controllerDestinationDir = `./app/${moduleName}/controller.js`;
49 | copyFile(controllerSourceDir, controllerDestinationDir, 'Controllers File');
50 |
51 | //replace with module name
52 | shell.sed('-i', 'REPLACE_ME', moduleName, controllerDestinationDir);
53 | shell.sed('-i', 'UPPER', capitalizeFirstLetter(moduleName), controllerDestinationDir);
54 | }
55 |
56 | copyAndReplaceControllerFile();
57 |
58 |
59 | const generateControllerMapper = ()=> {
60 | const file_content = fs.readFileSync('./app/controllerMapper.json');
61 |
62 | let controllerMapper = JSON.parse(file_content.toString());
63 |
64 | controllerMapper.import.push(`import ${moduleName} from `+"'"+`./${moduleName}/controller.js` +"';")
65 | controllerMapper.mapper[moduleName] = moduleName
66 |
67 | let controllerMapperText = "";
68 |
69 | controllerMapper.import.forEach((item)=> {
70 | controllerMapperText = controllerMapperText.concat(`${item} \n`)
71 | })
72 | controllerMapperText = controllerMapperText.concat(`\n`)
73 |
74 | let mapperObject = ""
75 | Object.keys(controllerMapper.mapper).forEach((item)=> {
76 | mapperObject = mapperObject.concat(` ${item}: ${item}, \n`)
77 | })
78 |
79 | let mapperObjectText = "export const CONTROLLER_MAPPER = {\n" +
80 | mapperObject +
81 | "}"
82 | controllerMapperText = controllerMapperText.concat(mapperObjectText)
83 |
84 | fs.writeFileSync('./app/controllerMapper.js', controllerMapperText)
85 | fs.writeFileSync('./app/controllerMapper.json', JSON.stringify(controllerMapper, null, 4))
86 | }
87 |
88 | generateControllerMapper();
89 |
90 |
91 | const copyAndReplaceModelFile = ()=> {
92 | //copy model.js file
93 | const modelSourceDir = `./${frameworkName}/module/database/model.js`;
94 | const modelDestinationDir = `./app/${moduleName}/model.js`;
95 | copyFile(modelSourceDir, modelDestinationDir, 'Model Files');
96 |
97 | //replace model.js with module name
98 | shell.sed('-i', 'REPLACE_ME', capitalizeFirstLetter(moduleName), modelDestinationDir);
99 | }
100 |
101 | copyAndReplaceModelFile();
102 |
103 |
104 | const generateModelMapper = ()=> {
105 | const file_content = fs.readFileSync('./database/modelMapper.json');
106 |
107 | let modelMapper = JSON.parse(file_content.toString());
108 |
109 | modelMapper.import.push(`import {Model as ${capitalizeFirstLetter(moduleName)}} from `+"'"+`../app/${moduleName}/model.js` +"';");
110 | modelMapper.export.push(capitalizeFirstLetter(moduleName));
111 |
112 | // move the relation import to the last position.
113 | modelMapper.import.push(modelMapper.import.splice(modelMapper.import.indexOf(`import relation from './relation.js';`), 1)[0]);
114 |
115 | let modelMapperText = "";
116 |
117 | modelMapper.import.forEach((item)=> {
118 | modelMapperText = modelMapperText.concat(`${item} \n`)
119 | })
120 | modelMapperText = modelMapperText.concat(`\n`)
121 |
122 | let exportObject = ""
123 | modelMapper.export.forEach((item)=> {
124 | exportObject = exportObject.concat(`${item},`)
125 | })
126 |
127 | let exportObjectText = "export {" + exportObject +"}"
128 | modelMapperText = modelMapperText.concat(exportObjectText)
129 |
130 | fs.writeFileSync('./database/modelMapper.js', modelMapperText)
131 | fs.writeFileSync('./database/modelMapper.json', JSON.stringify(modelMapper, null, 4))
132 | }
133 |
134 |
135 | generateModelMapper()
136 |
137 |
--------------------------------------------------------------------------------
/haste/module/removeModule.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import fs from 'fs';
4 | import shell from 'shelljs'
5 | import clear from 'clear'
6 | import chalk from "chalk"
7 | import figlet from 'figlet'
8 | import {frameworkName} from "../constant/general.js";
9 | import {capitalizeFirstLetter} from "../utils/utils.js";
10 |
11 | //Little style
12 | clear();
13 | console.log(
14 | chalk.yellow(
15 | figlet.textSync(frameworkName, { horizontalLayout: 'full' })
16 | )
17 | );
18 |
19 | // Get module name
20 | let moduleName = process.argv[2];
21 | moduleName = moduleName.toLowerCase();
22 |
23 | // User module can not be deleted
24 | if(moduleName === 'user'){
25 | console.log(chalk.red('User Module can not be removed!'));
26 | process.exit(1);
27 | }
28 |
29 | //Check a Module Exists or Not
30 | let dir = `./app/${moduleName}`;
31 | if(!fs.existsSync(dir)){
32 | console.log(chalk.red('Module Not Found!'))
33 | process.exit(1)
34 | }
35 |
36 | const removeDirectory = ()=> {
37 | shell.rm('-rf', dir)
38 | }
39 | removeDirectory();
40 |
41 | const removeControllerMapper = ()=> {
42 | const file_content = fs.readFileSync('./app/controllerMapper.json');
43 |
44 | let controllerMapper = JSON.parse(file_content.toString());
45 |
46 | const index = controllerMapper.import.indexOf(`import ${moduleName} from `+"'"+`./${moduleName}/controller.js` +"';");
47 | if (index > -1) {
48 | controllerMapper.import.splice(index, 1);
49 | }
50 |
51 | delete controllerMapper.mapper[moduleName]
52 |
53 | let controllerMapperText = "";
54 |
55 | controllerMapper.import.forEach((item)=> {
56 | controllerMapperText = controllerMapperText.concat(`${item} \n`)
57 | })
58 | controllerMapperText = controllerMapperText.concat(`\n`)
59 |
60 | let mapperObject = ""
61 | Object.keys(controllerMapper.mapper).forEach((item)=> {
62 | mapperObject = mapperObject.concat(` ${item}: ${item}, \n`)
63 | })
64 |
65 | let mapperObjectText = "export const CONTROLLER_MAPPER = {\n" +
66 | mapperObject +
67 | "}"
68 | controllerMapperText = controllerMapperText.concat(mapperObjectText)
69 |
70 | fs.writeFileSync('./app/controllerMapper.js', controllerMapperText)
71 | fs.writeFileSync('./app/controllerMapper.json', JSON.stringify(controllerMapper, null, 4))
72 | }
73 |
74 | removeControllerMapper();
75 |
76 | const removeModelMapper = ()=> {
77 | const file_content = fs.readFileSync('./database/modelMapper.json');
78 |
79 | let modelMapper = JSON.parse(file_content.toString());
80 |
81 | const index = modelMapper.import.indexOf(`import {Model as ${capitalizeFirstLetter(moduleName)}} from `+"'"+`../app/${moduleName}/model.js` +"';");
82 | if (index > -1) {
83 | modelMapper.import.splice(index, 1);
84 | }
85 |
86 | const objectIndex = modelMapper.export.indexOf(capitalizeFirstLetter(moduleName));
87 | if (objectIndex > -1) {
88 | modelMapper.export.splice(objectIndex, 1);
89 | }
90 |
91 | // move the relation import to the last position.
92 | modelMapper.import.push(modelMapper.import.splice(modelMapper.import.indexOf(`import relation from './relation.js';`), 1)[0]);
93 |
94 | let modelMapperText = "";
95 |
96 | modelMapper.import.forEach((item)=> {
97 | modelMapperText = modelMapperText.concat(`${item} \n`)
98 | })
99 | modelMapperText = modelMapperText.concat(`\n`)
100 |
101 | let exportObject = ""
102 | modelMapper.export.forEach((item)=> {
103 | exportObject = exportObject.concat(`${item},`)
104 | })
105 |
106 | let exportObjectText = "export {" + exportObject +"}"
107 | modelMapperText = modelMapperText.concat(exportObjectText)
108 |
109 | fs.writeFileSync('./database/modelMapper.js', modelMapperText)
110 | fs.writeFileSync('./database/modelMapper.json', JSON.stringify(modelMapper, null, 4))
111 | console.log(chalk.green("Module Removed Successfully!"));
112 | }
113 |
114 | removeModelMapper();
115 |
116 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/haste/module/routes.json:
--------------------------------------------------------------------------------
1 | {
2 | "routes": [
3 | {
4 | "method": "GET",
5 | "path": "/REPLACE_ME",
6 | "controller": "REPLACE_ME.find",
7 | "config": {
8 | "middleware": []
9 | }
10 | },
11 | {
12 | "method": "GET",
13 | "path": "/REPLACE_ME/count",
14 | "controller": "REPLACE_ME.count",
15 | "config": {
16 | "middleware": []
17 | }
18 | },
19 | {
20 | "method": "GET",
21 | "path": "/REPLACE_ME/:id",
22 | "controller": "REPLACE_ME.findOne",
23 | "config": {
24 | "middleware": []
25 | }
26 | },
27 | {
28 | "method": "POST",
29 | "path": "/REPLACE_ME",
30 | "controller": "REPLACE_ME.create",
31 | "config": {
32 | "middleware": []
33 | }
34 | },
35 | {
36 | "method": "PUT",
37 | "path": "/REPLACE_ME/:id",
38 | "controller": "REPLACE_ME.update",
39 | "config": {
40 | "middleware": []
41 | }
42 | },
43 | {
44 | "method": "DELETE",
45 | "path": "/REPLACE_ME/:id",
46 | "controller": "REPLACE_ME.delete",
47 | "config": {
48 | "middleware": []
49 | }
50 | }
51 | ]
52 | }
--------------------------------------------------------------------------------
/haste/utils/utils.js:
--------------------------------------------------------------------------------
1 | import fs from "fs";
2 | import shell from "shelljs";
3 | import chalk from "chalk";
4 |
5 | export const copyFile = (sourceDir, desDir, FileName = 'Files')=> {
6 | fs.copyFileSync(sourceDir, desDir);
7 | console.log(chalk.green(`${FileName} generated successfully!`));
8 | }
9 |
10 | export const readContent = (file) => {
11 | return fs.readFileSync(file);
12 | }
13 |
14 | export const combinedRoutes = ()=> {
15 | let result=[];
16 | shell.cd('app');
17 | shell.ls('./*/routes.json').forEach((file)=> {
18 | let data = readContent(file);
19 | result.push(...JSON.parse(data.toString()).routes)
20 | });
21 | shell.cd('..');
22 | return result;
23 | }
24 |
25 | export const capitalizeFirstLetter = (string) => {
26 | return string.charAt(0).toUpperCase() + string.slice(1);
27 | }
28 |
29 | export const isDuplicate = (file, string) => {
30 | const data = fs.readFileSync(file);
31 | return data.includes(string);
32 |
33 | }
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/middleware/isLoggedIn.js:
--------------------------------------------------------------------------------
1 | import jwt from 'jsonwebtoken';
2 | import {jwtSecretKey} from "../config.js";
3 |
4 | const isLoggedIn = (req,res,next) => {
5 | if(!req.get('Authorization')){
6 | const error = new Error("Unauthorized User!");
7 | error.statusCode = 401;
8 | throw error;
9 | }
10 | let token = req.get('Authorization').split(" ")[1];
11 | let decodedToken;
12 | try{
13 | decodedToken = jwt.verify(token, process.env.JWT_SECRET || jwtSecretKey);
14 | if(!decodedToken){
15 | const error = new Error("Unauthorized User!")
16 | error.statusCode = 403;
17 | throw error;
18 | }
19 | req.user_id = decodedToken.user_id
20 | req.user_email = decodedToken.email
21 | next()
22 | }catch (e) {
23 | const error = new Error("Unauthorized User!")
24 | error.statusCode = 403;
25 | next(error)
26 | }
27 | }
28 |
29 | export default isLoggedIn;
--------------------------------------------------------------------------------
/middleware/middlewareMapper.js:
--------------------------------------------------------------------------------
1 | import isLoggedIn from './isLoggedIn.js';
2 |
3 | export const MIDDLEWARE_MAPPER = {
4 | isLoggedIn: isLoggedIn,
5 | }
--------------------------------------------------------------------------------
/middleware/middlewareMapper.json:
--------------------------------------------------------------------------------
1 | {
2 | "import": [
3 | "import isLoggedIn from './isLoggedIn.js';"
4 | ],
5 | "mapper": {
6 | "isLoggedIn": "isLoggedIn"
7 | }
8 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hastejs-cli",
3 | "version": "0.0.9",
4 | "description": "A nodejs Framework for develop API in record time.",
5 | "repository": {
6 | "type": "git",
7 | "url": "https://github.com/sultanfendonus/HasteJS"
8 | },
9 | "main": "app/index.js",
10 | "type": "module",
11 | "scripts": {
12 | "start": "nodemon app/index.js",
13 | "develop": "nodemon app/index.js",
14 | "deploy": "pm2 start app/index.js",
15 | "test": "mocha ./test",
16 | "sequelize": "sequelize",
17 | "db:sync": "node database/sync.js",
18 | "export": "node haste/export/script.js",
19 | "create-module": "node haste/module/module.js",
20 | "remove-module": "node haste/module/removeModule.js",
21 | "create-middleware": "node haste/middleware/script.js",
22 | "remove-middleware": "node haste/middleware/removeMiddleware.js"
23 | },
24 | "bin": {
25 | "haste": "./bin/index.js",
26 | "create-module": "./haste/module/module.js",
27 | "remove-module": "./haste/module/removeModule.js",
28 | "create-middleware": "./haste/middleware/script.js",
29 | "remove-middleware": "./haste/middleware/removeMiddleware.js"
30 | },
31 | "author": "SULTAN MAHAMUD",
32 | "license": "MIT",
33 | "dependencies": {
34 | "bcryptjs": "^2.4.3",
35 | "body-parser": "1.19.0",
36 | "chai": "^4.2.0",
37 | "chai-http": "^4.3.0",
38 | "chalk": "^4.1.0",
39 | "clear": "^0.1.0",
40 | "cors": "2.8.5",
41 | "dotenv": "^8.2.0",
42 | "ejs": "^3.1.5",
43 | "express": "^4.17.1",
44 | "faker": "^5.1.0",
45 | "figlet": "^1.5.0",
46 | "fs-extra": "^9.0.1",
47 | "hastejs-cli": "^0.0.6",
48 | "jsonwebtoken": "^8.5.1",
49 | "mariadb": "^2.5.2",
50 | "mocha": "^8.2.1",
51 | "mysql2": "^2.2.5",
52 | "nodemon": "^2.0.6",
53 | "pg": "^8.5.1",
54 | "pg-hstore": "^2.3.3",
55 | "sequelize": "^6.3.5",
56 | "shelljs": "^0.8.4",
57 | "sqlite3": "^5.0.0",
58 | "yargs": "^16.2.0"
59 | },
60 | "devDependencies": {
61 | "cross-env": "^7.0.2",
62 | "sequelize-cli": "^6.2.0"
63 | },
64 | "keywords": [
65 | "hasteJs",
66 | "framework",
67 | "web",
68 | "rest",
69 | "api",
70 | "restful",
71 | "express",
72 | "hastejs-cli"
73 | ]
74 | }
75 |
--------------------------------------------------------------------------------
/public/roket.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sultanfendonus/HasteJS/5e32d348c706696192506eea61536517bc8d8eec/public/roket.png
--------------------------------------------------------------------------------
/test/mapper.js:
--------------------------------------------------------------------------------
1 | import chai from 'chai';
2 | import chaiHttp from 'chai-http';
3 | import fs from "fs";
4 |
5 | chai.should();
6 | chai.use(chaiHttp)
7 |
8 | let expect = chai.expect;
9 | let assert = chai.assert;
10 |
11 | describe("Check Existing of Mapper Files", ()=> {
12 | describe("Check existing of ControllerMapper",()=> {
13 | it('should check existing of controllerMapper.js and controllerMapper.json', function (done) {
14 | const controllerMapperJs = fs.existsSync('./app/controllerMapper.js');
15 | const controllerMapperJson = fs.existsSync('./app/controllerMapper.json');
16 | assert.equal(controllerMapperJs && controllerMapperJson, true)
17 | done()
18 | });
19 | })
20 |
21 | describe("Check existing of MiddlewareMapper",()=> {
22 | it('should check existing of middlewareMapper.js and middlewareMapper.json', function (done) {
23 | const middlewareMapperJs = fs.existsSync('./middleware/middlewareMapper.js');
24 | const middlewareMapperJson = fs.existsSync('./middleware/middlewareMapper.json');
25 | assert.equal(middlewareMapperJs && middlewareMapperJson, true)
26 | done()
27 | });
28 | })
29 | })
--------------------------------------------------------------------------------
/test/user.js:
--------------------------------------------------------------------------------
1 | import chai from 'chai';
2 | import chaiHttp from 'chai-http';
3 | import {server} from '../app/index.js'
4 | import fs from "fs";
5 | import faker from 'faker';
6 |
7 | chai.should();
8 | chai.use(chaiHttp)
9 |
10 | let expect = chai.expect;
11 | let assert = chai.assert;
12 |
13 | describe('User API', ()=> {
14 | describe("User Module found", ()=> {
15 | it("should find the User module and the child files", (done)=> {
16 | const route = fs.existsSync('./app/user/routes.json');
17 | const controller = fs.existsSync('./app/user/controller.js');
18 | const model = fs.existsSync('./app/user/model.js');
19 | assert.equal(route && controller && model, true)
20 | done()
21 | });
22 | })
23 | describe("User API", ()=> {
24 | describe("GET /user/", ()=> {
25 | it("should return 401 if I do not put Authorization token", (done)=>{
26 | chai.request(server)
27 | .get("/user/")
28 | .end((err, response)=> {
29 | response.should.have.status(401)
30 | done();
31 | })
32 | })
33 |
34 | it("should return 403 if I put invalid token", (done)=>{
35 | chai.request(server)
36 | .get("/user/")
37 | .set("Authorization", 'dummy token+dd')
38 | .end((err, response)=> {
39 | response.should.have.status(403)
40 | done();
41 | })
42 | })
43 |
44 | it("should return array list of users", (done)=> {
45 | chai.request(server)
46 | .get("/user/")
47 | .set('Authorization', 'token eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InN1bHRhbjE2NTJAZ21haWwuY29tIiwidXNlcl9pZCI6MTMsImlhdCI6MTYwNjc5NzU3Nn0.jgI54Nvo0se7v40QqAPFT-fe8FSnkUnUErfW0u6kmXs')
48 | .end((err, response)=> {
49 | response.should.have.status(200);
50 | response.body.should.be.a('array');
51 | done();
52 | })
53 | })
54 | })
55 |
56 | // describe("User Authentication, POST /user/register", ()=> {
57 | // let fakeEmail = faker.internet.email();
58 | // it("should able to register a random generated user", (done)=>{
59 | // chai.request(server)
60 | // .post("/user/register/")
61 | // .send({
62 | // first_name: faker.name.firstName(),
63 | // last_name: faker.name.lastName(),
64 | // email: fakeEmail,
65 | // password: "123456"
66 | // })
67 | // .end((err, response)=> {
68 | // response.should.have.status(201);
69 | // response.body.should.have.property('token');
70 | // done();
71 | // })
72 | // })
73 | //
74 | // it("It should able to be login", (done)=>{
75 | // chai.request(server)
76 | // .post('/user/login/')
77 | // .send({
78 | // email: fakeEmail,
79 | // password: '123456'
80 | // })
81 | // .end((err, response)=> {
82 | // response.should.have.status(200)
83 | // done();
84 | // })
85 | // })
86 | // })
87 | })
88 | })
89 |
--------------------------------------------------------------------------------
/views/api-docs.ejs:
--------------------------------------------------------------------------------
1 | <%- include('includes/header.ejs')%>
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%- include('includes/navbar.ejs')%>
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | # |
17 | Method |
18 | Endpoint |
19 | Url |
20 | Middleware |
21 |
22 |
23 |
24 | <% for(var i=0; i < api.length; i++) { %>
25 |
26 | <%= i+1%> |
27 | <% if(api[i].method === 'DELETE') { %>
28 | |
29 | <% }else if(api[i].method === 'POST') {%>
30 | |
31 | <% }else if(api[i].method === 'PUT') {%>
32 | |
33 | <% }else {%>
34 | |
35 | <% } %>
36 |
37 | <%= api[i].path %> |
38 | http://127.0.0.1:<%= port %><%= api[i].path %> |
39 | <%= api[i].config.middleware %> |
40 |
41 | <% } %>
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |