@anthonylzq/simba.js 1.10.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,7 +6,7 @@ Set up a modern backend app by running one command. This project has the goal to
6
6
 
7
7
  - `.env`
8
8
  - `.eslintignore`
9
- - `.eslintrc.js`
9
+ - `.eslintrc`
10
10
  - `.gitignore`
11
11
  - `CHANGELOG.md`
12
12
  - `Dockerfile`
@@ -15,8 +15,10 @@ Set up a modern backend app by running one command. This project has the goal to
15
15
  - `nodemon.json`
16
16
  - `package.json`
17
17
  - `README.md`
18
+ - `tsconfig.base.json`
18
19
  - `tsconfig.json`
19
20
  - `webpack.config.js`
21
+ - `yarn.lock` (or `package-lock.json`)
20
22
 
21
23
  ## Installation
22
24
 
@@ -95,34 +97,56 @@ Developed by AnthonyLzq
95
97
  Regardless of the option chosen, a new folder will be generated with the name of the project, it will contain the following structure:
96
98
 
97
99
  ```
98
- 📦node_modules
99
- 📦src
100
+ 📂node_modules
101
+ 📂src
100
102
  ┣ 📂@types
101
- ┃ ┣ 📜index.d.ts
102
- 📂controllers
103
- 📜index.ts
104
- 📜user.ts
105
- 📂dto-interfaces
106
- ┃ ┣ 📜index.ts
107
- ┃ ┗ 📜user.dto.ts
108
- 📂models
109
- 📜index.ts
110
- 📜user.ts
103
+ ┃ ┣ 📂custom
104
+ ┃ ┃ 📜request.d.ts
105
+ 📜response.d.ts
106
+ 📂dto
107
+ ┃ ┗ 📜user.d.ts
108
+ ┃ ┣ 📂models
109
+ ┗ 📜user.d.ts
110
+ ┗ 📜index.d.ts
111
+ 📂database
112
+ 📂mongo
113
+ ┃ ┃ ┣ 📂models
114
+ ┃ ┃ ┃ ┣ 📜index.ts
115
+ ┃ ┃ ┃ ┗ 📜user.ts
116
+ ┃ ┃ ┣ 📂queries
117
+ ┃ ┃ ┃ ┣ 📜index.ts
118
+ ┃ ┃ ┃ ┗ 📜user.ts
119
+ ┃ ┃ ┗ 📜index.ts
120
+ ┃ ┗ 📜index.ts
111
121
  ┣ 📂network
122
+ ┃ ┣ 📂routes
123
+ ┃ ┃ ┣ 📂schemas
124
+ ┃ ┃ ┃ ┣ 📜index.ts
125
+ ┃ ┃ ┃ ┗ 📜user.ts
126
+ ┃ ┃ ┣ 📜home.ts
127
+ ┃ ┃ ┣ 📜index.ts
128
+ ┃ ┃ ┗ 📜user.ts
112
129
  ┃ ┣ 📜index.ts
130
+ ┃ ┣ 📜response.ts
113
131
  ┃ ┣ 📜routes.ts
114
132
  ┃ ┗ 📜server.ts
115
- ┣ 📂routes
116
- ┃ ┣ 📜home.ts
133
+ ┣ 📂services
134
+ ┃ ┣ 📂utils
135
+ ┃ ┃ ┣ 📂messages
136
+ ┃ ┃ ┃ ┣ 📜index.ts
137
+ ┃ ┃ ┃ ┗ 📜user.ts
138
+ ┃ ┃ ┗ 📜index.ts
117
139
  ┃ ┣ 📜index.ts
118
140
  ┃ ┗ 📜user.ts
141
+ ┣ 📂test
142
+ ┃ ┗ 📜test.http
119
143
  ┣ 📂utils
120
- ┃ ┣ 📜index.ts
121
- ┃ ┗ 📜response.ts
144
+ ┃ ┣ 📜docs.json
145
+ ┃ ┗ 📜index.ts
122
146
  ┗ 📜index.ts
123
147
  📜.env
124
148
  📜.eslintignore
125
- 📜.eslintrc.js
149
+ 📜.eslintrc
126
150
  📜.gitignore
127
151
  📜CHANGELOG.md
128
152
  📜Dockerfile
@@ -131,6 +155,7 @@ Regardless of the option chosen, a new folder will be generated with the name of
131
155
  📜nodemon.json
132
156
  📜package.json
133
157
  📜README.md
158
+ 📜tsconfig.base.json
134
159
  📜tsconfig.json
135
160
  📜webpack.config.js
136
161
  📜yarn.lock (or package-lock.json)
@@ -160,8 +185,8 @@ simba -N myProject -D 'This is a test' -a myName -e myEmail@email.com -H
160
185
 
161
186
  ### Some considerations
162
187
 
163
- - This project is based in other project from my own, [`typescript-project-generator`](https://www.npmjs.com/package/typescript-project-generator), but only considering the `express-mongoose-node` part.
164
- - You are able to run a server that has one main route, `home` (`/`), `user` (`api/user` or `api/user/:userId`) and `docs` (`api/docs`).
188
+ - This project was based in other project from my own, [`typescript-project-generator`](https://www.npmjs.com/package/typescript-project-generator), but only considering the `express-mongoose` part.
189
+ - You are able to run a server that has one main route, `home` (`/`), `user` (`api/user` or `api/user/:id`) and `docs` (`api/docs`).
165
190
  - To connect your server with your `MongoDB` database, you need to provide your `uri` in the `.env`. By default, Simba will try to connect to a local database. The content of the `.env` file is:
166
191
 
167
192
  ```bash
@@ -182,11 +207,16 @@ simba -N myProject -D 'This is a test' -a myName -e myEmail@email.com -H
182
207
  // Some more code...
183
208
 
184
209
  // another file
185
- globalStringVariable = 'Hi mom, I am global'
210
+ global.globalStringVariable = 'Hi mom, I am global'
186
211
  console.log({ globalStringVariable })
187
212
  ```
188
213
 
189
- - The provided project structure is inspired in my personal experience as [`Node.js`](https://nodejs.org/en/) developer and the [`Nest`](https://nestjs.com/) framework.
214
+ - The provided project structure is inspired in my personal experience as [`Node.js`](https://nodejs.org/en/) developer and the [`Nest`](https://nestjs.com/) framework. It follows a layered architecture:
215
+
216
+ 1. Presentation layer (network layer): it is represented by the network folder, which contains the routes and the necessary schemas for each route.
217
+ 2. Business layer (services layer): it is represented by the services folder, which contains all the code related to the business logic of your application.
218
+ 3. Persistance layer (database layer): it is represented by the database folder, which contains the database connection, models and queries (that will be used by the services). Multiple database connection are possible and should be implemented here.
219
+
190
220
  - The server is fully tested and has no errors (at least for now), feel free to report one [here](https://github.com/AnthonyLzq/simba.js/issues).
191
221
  - Support for windows and linux platforms is available.
192
222
  - To check the content of the files generated, please check the `example` folder.
@@ -215,8 +245,6 @@ Here is the list of the packages that are being installed, as `devDependencies`:
215
245
  - [`eslint-config-standard`](https://www.npmjs.com/package/eslint-config-standard)
216
246
  - [`eslint-plugin-import`](https://www.npmjs.com/package/eslint-plugin-import)
217
247
  - [`eslint-plugin-prettier`](https://www.npmjs.com/package/eslint-plugin-prettier)
218
- - [`eslint-plugin-sort-keys-fix`](https://www.npmjs.com/package/eslint-plugin-sort-keys-fix)
219
- - [`eslint-plugin-typescript-sort-keys`](https://www.npmjs.com/package/eslint-plugin-typescript-sort-keys)
220
248
  - [`nodemon`](https://www.npmjs.com/package/nodemon)
221
249
  - [`prettier`](https://www.npmjs.com/package/prettier)
222
250
  - [`standard-version`](https://www.npmjs.com/package/standard-version)
@@ -14,11 +14,11 @@ COPY package.json ./
14
14
 
15
15
  RUN yarn install --prod
16
16
 
17
- RUN yarn add webpack webpack-node-externals -D
17
+ RUN yarn add webpack webpack-node-externals tsconfig-paths-webpack-plugin -D
18
18
 
19
19
  RUN yarn build
20
20
 
21
- RUN yarn remove webpack webpack-node-externals
21
+ RUN yarn remove webpack webpack-node-externals tsconfig-paths-webpack-plugin
22
22
 
23
23
  COPY dist /app/dist
24
24
 
@@ -74,7 +74,7 @@ module.exports = async projectName => {
74
74
  }
75
75
  }
76
76
  `,
77
- eslintFile: '.eslintrc.js',
77
+ eslintFile: '.eslintrc',
78
78
  eslintIgnoreContent: '/dist',
79
79
  eslintIgnoreFile: '.eslintignore'
80
80
  }
@@ -5,30 +5,39 @@ const writeFile = require('../utils/writeFile')
5
5
  /*
6
6
  * src
7
7
  * |- @types:
8
+ * |- |- dto:
9
+ * |- |- |- user: content, file
10
+ * |- |- models:
11
+ * |- |- |- user: content, file
8
12
  * | |- index: content, file
9
- * |- controllers:
10
- * | |- utils:
11
- * | | |- messages:
13
+ * |- database:
14
+ * | |- mongo:
15
+ * | |- |- models:
16
+ * | |- |- |- index: content, file
17
+ * | |- |- |- user: content, file
18
+ * | |- |- queries:
19
+ * | |- |- |- index: content, file
20
+ * | |- |- |- user: content, file
21
+ * | |- |- index: content, file
22
+ * | |- index: content, file
23
+ * |- network:
24
+ * | |- routes:
25
+ * | | |- schemas:
12
26
  * | | | |- user: content, file
13
27
  * | | | |- index: content, file
28
+ * | | |- home: content, file
29
+ * | | |- user: content, file
14
30
  * | | |- index: content, file
15
- * | |- user: content, file
16
- * | |- index: content, file
17
- * |- dto-interfaces:
18
- * | |- user: content, file
19
- * | |- index: content, file
20
- * |- models:
21
- * | |- user: content, file
22
- * | |- index: content, file
23
- * |- network:
31
+ * | | |- response: content, file
32
+ * | |- router: content, file
24
33
  * | |- server: content, file
25
- * | |- routes: content, file
26
- * | |- index: content, file
27
- * |- routes:
28
- * | |- home: content, file
29
- * | |- user: content, file
30
34
  * | |- index: content, file
31
- * |- schemas:
35
+ * |- services:
36
+ * | |- utils:
37
+ * | | |- messages:
38
+ * | | | |- user: content, file
39
+ * | | | |- index: content, file
40
+ * | | |- index: content, file
32
41
  * | |- user: content, file
33
42
  * | |- index: content, file
34
43
  * |- test:
@@ -50,268 +59,86 @@ module.exports = async (projectName, projectVersion, email) => {
50
59
  '@types': {
51
60
  index: {
52
61
  content: `/* eslint-disable no-var */
53
- import { IncomingHttpHeaders } from 'http'
54
- import { Request, Response } from 'express'
55
-
56
- import { DtoUser } from 'dto-interfaces'
57
-
58
- declare global {
59
- interface ResponseProps {
60
- error: boolean
61
- message: unknown
62
- res: Response
63
- status: number
64
- }
65
- // This variable is global, so it will be available everywhere in the code
66
- var response: ({ error, message, res, status }: ResponseProps) => void
67
-
68
- // We can personalize the response and request objects in case we need it by
69
- // adding new optional attributes to this interface
70
- interface CustomResponse extends Response {
71
- newValue?: string
72
- }
73
-
74
- interface CustomRequest extends Request {
75
- body: {
76
- args?: DtoUser
77
- }
78
- // We can add custom headers via intersection, remember that for some reason
79
- // headers must be in Snake-Pascal-Case
80
- headers: IncomingHttpHeaders & {
81
- 'Custom-Header'?: string
82
- }
83
- }
84
- }
62
+ declare global {}
85
63
 
86
64
  export {}
87
65
  `,
88
66
  file: `${projectName}/src/@types/index.d.ts`
89
67
  }
90
68
  },
91
- controllers: {
92
- index: {
93
- content: `import { User } from './user'
94
-
95
- export { User }
96
- `,
97
- file: `${projectName}/src/controllers/index.ts`
98
- },
69
+ '@types/dto': {
99
70
  user: {
100
- content: `import httpErrors from 'http-errors'
101
-
102
- import { DtoUser } from 'dto-interfaces'
103
- import { IUser, UserModel } from 'models'
104
- import { EFU, MFU, GE, errorHandling } from './utils'
105
-
106
- type Process = {
107
- type: 'store' | 'getAll' | 'deleteAll' | 'getOne' | 'update' | 'delete'
71
+ content: `interface DtoUser {
72
+ id?: string
73
+ lastName?: string
74
+ name?: string
108
75
  }
76
+ `,
77
+ file: `${projectName}/src/@types/dto/user.d.ts`
78
+ }
79
+ },
80
+ '@types/custom': {
81
+ request: {
82
+ content: `type ExpressRequest = import('express').Request
109
83
 
110
- class User {
111
- private _args: DtoUser | null
112
-
113
- constructor(args: DtoUser | null = null) {
114
- this._args = args
115
- }
116
-
117
- public process({
118
- type
119
- }: Process): Promise<string> | Promise<IUser[]> | Promise<IUser> {
120
- switch (type) {
121
- case 'store':
122
- return this._store()
123
- case 'getAll':
124
- return this._getAll()
125
- case 'deleteAll':
126
- return this._deleteAll()
127
- case 'getOne':
128
- return this._getOne()
129
- case 'update':
130
- return this._update()
131
- case 'delete':
132
- return this._delete()
133
- default:
134
- throw new httpErrors.InternalServerError(GE.INTERNAL_SERVER_ERROR)
135
- }
136
- }
137
-
138
- private async _store(): Promise<IUser> {
139
- const { lastName, name } = this._args as DtoUser
140
-
141
- try {
142
- const newUser = new UserModel({ lastName, name })
143
- const result = await newUser.save()
144
-
145
- return result
146
- } catch (e) {
147
- return errorHandling(e, GE.INTERNAL_SERVER_ERROR)
148
- }
149
- }
150
-
151
- private async _getAll(): Promise<IUser[]> {
152
- try {
153
- const users = await UserModel.find({})
154
-
155
- return users
156
- } catch (e) {
157
- return errorHandling(e, GE.INTERNAL_SERVER_ERROR)
158
- }
159
- }
160
-
161
- private async _deleteAll(): Promise<string> {
162
- try {
163
- const usersDeleted = await UserModel.deleteMany({})
164
-
165
- if (usersDeleted.deletedCount >= 1) return MFU.ALL_USERS_DELETED
166
-
167
- if (usersDeleted.deletedCount === 0)
168
- throw new httpErrors.BadRequest(EFU.NOTHING_TO_DELETE)
169
-
170
- throw new httpErrors.InternalServerError(GE.INTERNAL_SERVER_ERROR)
171
- } catch (e) {
172
- return errorHandling(e, GE.INTERNAL_SERVER_ERROR)
173
- }
174
- }
175
-
176
- private async _getOne(): Promise<IUser> {
177
- const { id } = this._args as DtoUser
178
-
179
- try {
180
- const user = await UserModel.findById(id)
181
-
182
- if (!user) throw new httpErrors.NotFound(EFU.NOT_FOUND)
183
-
184
- return user
185
- } catch (e) {
186
- return errorHandling(e, GE.INTERNAL_SERVER_ERROR)
187
- }
84
+ interface CustomRequest extends ExpressRequest {
85
+ body: {
86
+ args?: DtoUser
188
87
  }
189
-
190
- private async _update(): Promise<IUser> {
191
- const { id, lastName, name } = this._args as DtoUser
192
-
193
- try {
194
- const updatedUser = await UserModel.findByIdAndUpdate(
195
- id,
196
- { lastName, name },
197
- { new: true }
198
- )
199
-
200
- if (!updatedUser) throw new httpErrors.NotFound(EFU.NOT_FOUND)
201
-
202
- return updatedUser
203
- } catch (e) {
204
- return errorHandling(e, GE.INTERNAL_SERVER_ERROR)
205
- }
206
- }
207
-
208
- private async _delete(): Promise<string> {
209
- const { id } = this._args as DtoUser
210
-
211
- try {
212
- const deletedUser = await UserModel.findByIdAndRemove(id)
213
-
214
- if (!deletedUser) throw new httpErrors.NotFound(EFU.NOT_FOUND)
215
-
216
- return MFU.USER_DELETED
217
- } catch (e) {
218
- return errorHandling(e, GE.INTERNAL_SERVER_ERROR)
219
- }
88
+ // We can add custom headers via intersection, remember that for some reason
89
+ // headers must be in Snake-Pascal-Case
90
+ headers: import('http').IncomingHttpHeaders & {
91
+ 'Custom-Header'?: string
220
92
  }
221
93
  }
222
-
223
- export { User }
224
94
  `,
225
- file: `${projectName}/src/controllers/user.ts`
226
- }
227
- },
228
- 'controllers/utils': {
229
- index: {
230
- content: `import httpErrors from 'http-errors'
231
-
232
- import { EFU, MFU, GE } from './messages'
233
-
234
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
235
- const errorHandling = (e: any, message?: string): never => {
236
- console.error(e)
237
-
238
- if (e instanceof httpErrors.HttpError) throw e
95
+ file: `${projectName}/src/@types/custom/request.d.ts`
96
+ },
97
+ response: {
98
+ content: `type ExpressResponse = import('express').Response
239
99
 
240
- throw new httpErrors.InternalServerError(message ?? e.message)
100
+ interface CustomResponse extends ExpressResponse {
101
+ newValue?: string
241
102
  }
242
-
243
- export { EFU, MFU, GE, errorHandling }
244
103
  `,
245
- file: `${projectName}/src/controllers/utils/index.ts`
104
+ file: `${projectName}/src/@types/custom/response.d.ts`
246
105
  }
247
106
  },
248
- 'controllers/utils/messages': {
249
- index: {
250
- content: `import { EFU, MFU } from './user'
251
-
252
- enum GenericErrors {
253
- INTERNAL_SERVER_ERROR = 'Something went wrong'
254
- }
255
-
256
- export { EFU, MFU, GenericErrors as GE }
257
- `,
258
- file: `${projectName}/src/controllers/utils/messages/index.ts`
259
- },
107
+ '@types/models': {
260
108
  user: {
261
- content: `enum ErrorForUser {
262
- NOT_FOUND = 'The requested user does not exists',
263
- NOTHING_TO_DELETE = 'There is no user to be deleted'
264
- }
265
-
266
- enum MessageForUser {
267
- ALL_USERS_DELETED = 'All the users were deleted successfully',
268
- USER_DELETED = 'The requested user was successfully deleted'
109
+ content: `interface IUser {
110
+ _id: import('mongoose').Types.ObjectId
111
+ name: string
112
+ lastName: string
113
+ updatedAt: Date
269
114
  }
270
-
271
- export { ErrorForUser as EFU, MessageForUser as MFU }
272
115
  `,
273
- file: `${projectName}/src/controllers/utils/messages/user.ts`
116
+ file: `${projectName}/src/@types/models/user.d.ts`
274
117
  }
275
118
  },
276
- 'dto-interfaces': {
119
+ database: {
277
120
  index: {
278
- content: `import { DtoUser } from './user'
279
-
280
- export { DtoUser }
281
- `,
282
- file: `${projectName}/src/dto-interfaces/index.ts`
283
- },
284
- user: {
285
- content: `interface DtoUser {
286
- id: string
287
- lastName?: string
288
- name?: string
289
- }
290
-
291
- export { DtoUser }
292
- `,
293
- file: `${projectName}/src/dto-interfaces/user.ts`
121
+ content: "export * from './mongo'\n",
122
+ file: `${projectName}/src/database/index.ts`
294
123
  }
295
124
  },
296
- models: {
125
+ 'database/mongo': {
297
126
  index: {
298
- content: `import { IUser, UserModel } from './user'
299
-
300
- export { IUser, UserModel }
127
+ content: `export * from './models'
128
+ export * from './queries'
301
129
  `,
302
- file: `${projectName}/src/models/index.ts`
130
+ file: `${projectName}/src/database/mongo/index.ts`
131
+ }
132
+ },
133
+ 'database/mongo/models': {
134
+ index: {
135
+ content: "export * from './user'\n",
136
+ file: `${projectName}/src/database/mongo/models/index.ts`
303
137
  },
304
138
  user: {
305
- content: `/* eslint-disable no-underscore-dangle */
306
- import { Document, model, Schema } from 'mongoose'
139
+ content: `import { model, Schema } from 'mongoose'
307
140
 
308
- interface IUser extends Document {
309
- lastName: string
310
- name: string
311
- updatedAt: Date
312
- }
313
-
314
- const User = new Schema(
141
+ const User = new Schema<IUser>(
315
142
  {
316
143
  lastName: {
317
144
  required: true,
@@ -326,36 +153,98 @@ const User = new Schema(
326
153
  timestamps: {
327
154
  createdAt: false,
328
155
  updatedAt: true
156
+ },
157
+ toJSON: {
158
+ transform(_, ret) {
159
+ ret.id = ret._id
160
+ delete ret._id
161
+ delete ret.__v
162
+ delete ret.updatedAt
163
+ },
164
+ versionKey: false,
165
+ virtuals: true
329
166
  }
330
167
  }
331
168
  )
332
169
 
333
- User.set('toJSON', {
334
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
335
- transform: function (_: any, ret: any) {
336
- ret.id = ret._id
337
- delete ret._id
338
- delete ret.__v
339
- delete ret.updatedAt
340
- },
341
- versionKey: false,
342
- virtuals: true
343
- })
344
-
345
170
  const UserModel = model<IUser>('users', User)
346
171
 
347
- export { IUser, UserModel }
172
+ export { UserModel }
348
173
  `,
349
- file: `${projectName}/src/models/user.ts`
174
+ file: `${projectName}/src/database/mongo/models/user.ts`
175
+ }
176
+ },
177
+ 'database/mongo/queries': {
178
+ index: {
179
+ content: "export * from './user'\n",
180
+ file: `${projectName}/src/database/mongo/queries/index.ts`
181
+ },
182
+ user: {
183
+ content: `import { UserModel } from '../models'
184
+
185
+ const store = async (userData: DtoUser): Promise<IUser> => {
186
+ const user = new UserModel(userData)
187
+
188
+ return await user.save()
189
+ }
190
+
191
+ const remove = async (
192
+ id: string | null = null
193
+ ): Promise<IUser | number | null> => {
194
+ if (id) return await UserModel.findByIdAndRemove(id)
195
+
196
+ return (await UserModel.deleteMany({})).deletedCount
197
+ }
198
+
199
+ const get = async (
200
+ id: string | null = null
201
+ ): Promise<IUser[] | IUser | null> => {
202
+ if (id) return await UserModel.findById(id)
203
+
204
+ return await UserModel.find({})
205
+ }
206
+
207
+ const update = async (userData: DtoUser): Promise<IUser | null> => {
208
+ const { id, ...rest } = userData
209
+
210
+ return await UserModel.findByIdAndUpdate(id, rest, { new: true })
211
+ }
212
+
213
+ export { store, remove, get, update }
214
+ `,
215
+ file: `${projectName}/src/database/mongo/queries/user.ts`
350
216
  }
351
217
  },
352
218
  network: {
353
- routes: {
219
+ index: {
220
+ content: `export * from './routes'
221
+ export * from './server'
222
+ `,
223
+ file: `${projectName}/src/network/index.ts`
224
+ },
225
+ response: {
226
+ content: `interface ResponseProps {
227
+ error: boolean
228
+ message: unknown
229
+ res: CustomResponse
230
+ status: number
231
+ }
232
+
233
+ const response = ({ error, message, res, status }: ResponseProps): void => {
234
+ res.status(status).send({ error, message })
235
+ }
236
+
237
+ export { response }
238
+ `,
239
+ file: `${projectName}/src/network/response.ts`
240
+ },
241
+ router: {
354
242
  content: `import { Application, Response, Request, Router, NextFunction } from 'express'
355
243
  import swaggerUi from 'swagger-ui-express'
356
244
  import httpErrors from 'http-errors'
357
245
 
358
- import { Home, User } from 'routes'
246
+ import { response } from './response'
247
+ import { Home, User } from './routes'
359
248
  import { docs } from 'utils'
360
249
 
361
250
  const routers = [User]
@@ -389,14 +278,14 @@ const applyRoutes = (app: Application): void => {
389
278
 
390
279
  export { applyRoutes }
391
280
  `,
392
- file: `${projectName}/src/network/routes.ts`
281
+ file: `${projectName}/src/network/router.ts`
393
282
  },
394
283
  server: {
395
284
  content: `import express from 'express'
396
285
  import mongoose from 'mongoose'
397
286
  import morgan from 'morgan'
398
287
 
399
- import { applyRoutes } from './routes'
288
+ import { applyRoutes } from './router'
400
289
 
401
290
  const PORT = (process.env.PORT as string) || '1996'
402
291
 
@@ -430,16 +319,6 @@ class Server {
430
319
  }
431
320
  )
432
321
 
433
- // setting up the global response
434
- global.response = ({
435
- error,
436
- message,
437
- res,
438
- status
439
- }: ResponseProps): void => {
440
- res.status(status).send({ error, message })
441
- }
442
-
443
322
  applyRoutes(this._app)
444
323
  }
445
324
 
@@ -495,20 +374,14 @@ const server = new Server()
495
374
  export { server as Server }
496
375
  `,
497
376
  file: `${projectName}/src/network/server.ts`
498
- },
499
- index: {
500
- content: `import { applyRoutes } from './routes'
501
- import { Server } from './server'
502
-
503
- export { applyRoutes, Server }
504
- `,
505
- file: `${projectName}/src/network/index.ts`
506
377
  }
507
378
  },
508
- routes: {
379
+ 'network/routes': {
509
380
  home: {
510
381
  content: `import { Response, Request, Router } from 'express'
511
382
 
383
+ import { response } from 'network/response'
384
+
512
385
  const Home = Router()
513
386
 
514
387
  Home.route('').get((req: Request, res: Response) => {
@@ -522,24 +395,22 @@ Home.route('').get((req: Request, res: Response) => {
522
395
 
523
396
  export { Home }
524
397
  `,
525
- file: `${projectName}/src/routes/home.ts`
398
+ file: `${projectName}/src/network/routes/home.ts`
526
399
  },
527
400
  index: {
528
- content: `import { Home } from './home'
529
- import { User } from './user'
530
-
531
- export { Home, User }
401
+ content: `export * from './home'
402
+ export * from './user'
532
403
  `,
533
- file: `${projectName}/src/routes/index.ts`
404
+ file: `${projectName}/src/network/routes/index.ts`
534
405
  },
535
406
  user: {
536
407
  content: `import { Router, NextFunction } from 'express'
537
408
  import httpErrors from 'http-errors'
538
409
  import { ValidationError } from 'joi'
539
410
 
540
- import { User as UserC } from 'controllers/user'
541
- import { DtoUser } from 'dto-interfaces'
542
- import { idSchema, userSchema } from 'schemas'
411
+ import { response } from 'network/response'
412
+ import { UserService } from 'services/user'
413
+ import { idSchema, userSchema } from './schemas'
543
414
 
544
415
  const User = Router()
545
416
 
@@ -553,10 +424,10 @@ User.route('/users')
553
424
  const {
554
425
  body: { args }
555
426
  } = req
556
- const u = new UserC(args as DtoUser)
427
+ const us = new UserService(args as DtoUser)
557
428
 
558
429
  try {
559
- const result = await u.process({ type: 'store' })
430
+ const result = await us.process({ type: 'store' })
560
431
  response({ error: false, message: result, res, status: 201 })
561
432
  } catch (e) {
562
433
  next(e)
@@ -569,10 +440,10 @@ User.route('/users')
569
440
  res: CustomResponse,
570
441
  next: NextFunction
571
442
  ): Promise<void> => {
572
- const u = new UserC()
443
+ const us = new UserService()
573
444
 
574
445
  try {
575
- const result = await u.process({ type: 'getAll' })
446
+ const result = await us.process({ type: 'getAll' })
576
447
  response({ error: false, message: result, res, status: 200 })
577
448
  } catch (e) {
578
449
  next(e)
@@ -585,10 +456,10 @@ User.route('/users')
585
456
  res: CustomResponse,
586
457
  next: NextFunction
587
458
  ): Promise<void> => {
588
- const u = new UserC()
459
+ const us = new UserService()
589
460
 
590
461
  try {
591
- const result = await u.process({ type: 'deleteAll' })
462
+ const result = await us.process({ type: 'deleteAll' })
592
463
  response({ error: false, message: result, res, status: 200 })
593
464
  } catch (e) {
594
465
  next(e)
@@ -609,8 +480,8 @@ User.route('/user/:id')
609
480
 
610
481
  try {
611
482
  await idSchema.validateAsync(id)
612
- const u = new UserC({ id } as DtoUser)
613
- const result = await u.process({ type: 'getOne' })
483
+ const us = new UserService({ id } as DtoUser)
484
+ const result = await us.process({ type: 'getOne' })
614
485
  response({ error: false, message: result, res, status: 200 })
615
486
  } catch (e) {
616
487
  if (e instanceof ValidationError)
@@ -637,8 +508,8 @@ User.route('/user/:id')
637
508
 
638
509
  try {
639
510
  await userSchema.validateAsync(user)
640
- const u = new UserC(user)
641
- const result = await u.process({ type: 'update' })
511
+ const us = new UserService(user)
512
+ const result = await us.process({ type: 'update' })
642
513
  response({ error: false, message: result, res, status: 200 })
643
514
  } catch (e) {
644
515
  if (e instanceof ValidationError)
@@ -660,8 +531,8 @@ User.route('/user/:id')
660
531
 
661
532
  try {
662
533
  await idSchema.validateAsync(id)
663
- const u = new UserC({ id } as DtoUser)
664
- const result = await u.process({ type: 'delete' })
534
+ const us = new UserService({ id } as DtoUser)
535
+ const result = await us.process({ type: 'delete' })
665
536
  response({ error: false, message: result, res, status: 200 })
666
537
  } catch (e) {
667
538
  if (e instanceof ValidationError)
@@ -674,20 +545,19 @@ User.route('/user/:id')
674
545
 
675
546
  export { User }
676
547
  `,
677
- file: `${projectName}/src/routes/user.ts`
548
+ file: `${projectName}/src/network/routes/user.ts`
678
549
  }
679
550
  },
680
- schemas: {
551
+ 'network/routes/schemas': {
681
552
  index: {
682
553
  content: `import Joi from 'joi'
683
554
 
684
- import { userSchema } from './user'
685
-
686
555
  const idSchema = Joi.string().length(24).required()
687
556
 
688
- export { idSchema, userSchema }
557
+ export { idSchema }
558
+ export * from './user'
689
559
  `,
690
- file: `${projectName}/src/schemas/index.ts`
560
+ file: `${projectName}/src/network/routes/schemas/index.ts`
691
561
  },
692
562
  user: {
693
563
  content: `import Joi from 'joi'
@@ -700,7 +570,175 @@ const userSchema = Joi.object().keys({
700
570
 
701
571
  export { userSchema }
702
572
  `,
703
- file: `${projectName}/src/schemas/user.ts`
573
+ file: `${projectName}/src/network/routes/schemas/user.ts`
574
+ }
575
+ },
576
+ services: {
577
+ index: {
578
+ content: "export * from './user'\n",
579
+ file: `${projectName}/src/services/index.ts`
580
+ },
581
+ user: {
582
+ content: `import httpErrors from 'http-errors'
583
+
584
+ import { store, remove, get, update } from 'database'
585
+ import { EFU, MFU, GE, errorHandling } from './utils'
586
+
587
+ type Process = {
588
+ type: 'store' | 'getAll' | 'deleteAll' | 'getOne' | 'update' | 'delete'
589
+ }
590
+
591
+ class UserService {
592
+ private _args: DtoUser | null
593
+
594
+ constructor(args: DtoUser | null = null) {
595
+ this._args = args
596
+ }
597
+
598
+ public process({ type }: Process): Promise<string | IUser[] | IUser> {
599
+ switch (type) {
600
+ case 'store':
601
+ return this._store()
602
+ case 'getAll':
603
+ return this._getAll()
604
+ case 'deleteAll':
605
+ return this._deleteAll()
606
+ case 'getOne':
607
+ return this._getOne()
608
+ case 'update':
609
+ return this._update()
610
+ case 'delete':
611
+ return this._delete()
612
+ default:
613
+ throw new httpErrors.InternalServerError(GE.INTERNAL_SERVER_ERROR)
614
+ }
615
+ }
616
+
617
+ private async _store(): Promise<IUser> {
618
+ try {
619
+ const result = await store(this._args as DtoUser)
620
+
621
+ return result
622
+ } catch (e) {
623
+ return errorHandling(e, GE.INTERNAL_SERVER_ERROR)
624
+ }
625
+ }
626
+
627
+ private async _getAll(): Promise<IUser[]> {
628
+ try {
629
+ const users = (await get()) as IUser[]
630
+
631
+ return users
632
+ } catch (e) {
633
+ return errorHandling(e, GE.INTERNAL_SERVER_ERROR)
634
+ }
635
+ }
636
+
637
+ private async _deleteAll(): Promise<string> {
638
+ try {
639
+ const usersDeleted = (await remove()) as number
640
+
641
+ if (usersDeleted >= 1) return MFU.ALL_USERS_DELETED
642
+
643
+ if (usersDeleted === 0)
644
+ throw new httpErrors.Conflict(EFU.NOTHING_TO_DELETE)
645
+
646
+ throw new httpErrors.InternalServerError(GE.INTERNAL_SERVER_ERROR)
647
+ } catch (e) {
648
+ return errorHandling(e, GE.INTERNAL_SERVER_ERROR)
649
+ }
650
+ }
651
+
652
+ private async _getOne(): Promise<IUser> {
653
+ const { id } = this._args as DtoUser
654
+
655
+ try {
656
+ const user = (await get(id as string)) as IUser | null
657
+
658
+ if (!user) throw new httpErrors.NotFound(EFU.NOT_FOUND)
659
+
660
+ return user
661
+ } catch (e) {
662
+ return errorHandling(e, GE.INTERNAL_SERVER_ERROR)
663
+ }
664
+ }
665
+
666
+ private async _update(): Promise<IUser> {
667
+ try {
668
+ const updatedUser = await update(this._args as DtoUser)
669
+
670
+ if (!updatedUser) throw new httpErrors.NotFound(EFU.NOT_FOUND)
671
+
672
+ return updatedUser
673
+ } catch (e) {
674
+ return errorHandling(e, GE.INTERNAL_SERVER_ERROR)
675
+ }
676
+ }
677
+
678
+ private async _delete(): Promise<string> {
679
+ const { id } = this._args as DtoUser
680
+
681
+ try {
682
+ const deletedUser = await remove(id)
683
+
684
+ if (!deletedUser) throw new httpErrors.NotFound(EFU.NOT_FOUND)
685
+
686
+ return MFU.USER_DELETED
687
+ } catch (e) {
688
+ return errorHandling(e, GE.INTERNAL_SERVER_ERROR)
689
+ }
690
+ }
691
+ }
692
+
693
+ export { UserService }
694
+ `,
695
+ file: `${projectName}/src/services/user.ts`
696
+ }
697
+ },
698
+ 'services/utils': {
699
+ index: {
700
+ content: `import httpErrors from 'http-errors'
701
+
702
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
703
+ const errorHandling = (e: any, message?: string): never => {
704
+ console.error(e)
705
+
706
+ if (e instanceof httpErrors.HttpError) throw e
707
+
708
+ throw new httpErrors.InternalServerError(message ?? e.message)
709
+ }
710
+
711
+ export { errorHandling }
712
+ export * from './messages'
713
+ `,
714
+ file: `${projectName}/src/services/utils/index.ts`
715
+ }
716
+ },
717
+ 'services/utils/messages': {
718
+ index: {
719
+ content: `enum GenericErrors {
720
+ INTERNAL_SERVER_ERROR = 'Something went wrong'
721
+ }
722
+
723
+ export { GenericErrors as GE }
724
+ export * from './user'
725
+ `,
726
+ file: `${projectName}/src/services/utils/messages/index.ts`
727
+ },
728
+ user: {
729
+ content: `enum ErrorForUser {
730
+ NOT_FOUND = 'The requested user does not exists',
731
+ NOTHING_TO_DELETE = 'There is no user to be deleted'
732
+ }
733
+
734
+ enum MessageForUser {
735
+ ALL_USERS_DELETED = 'All the users were deleted successfully',
736
+ USER_DELETED = 'The requested user was successfully deleted'
737
+ }
738
+
739
+ export { ErrorForUser as EFU, MessageForUser as MFU }
740
+ `,
741
+ file: `${projectName}/src/services/utils/messages/user.ts`
704
742
  }
705
743
  },
706
744
  test: {
@@ -1164,10 +1202,7 @@ DELETE http://localhost:1996/api/user/60e7e3b93b01c1a7aa74cd6b
1164
1202
  file: `${projectName}/src/utils/docs.json`
1165
1203
  },
1166
1204
  index: {
1167
- content: `import docs from './docs.json'
1168
-
1169
- export { docs }
1170
- `,
1205
+ content: "export { default as docs } from './docs.json'\n",
1171
1206
  file: `${projectName}/src/utils/index.ts`
1172
1207
  }
1173
1208
  },
@@ -1186,14 +1221,19 @@ Server.start()
1186
1221
 
1187
1222
  await exec(`mkdir ${projectName}/src \
1188
1223
  ${projectName}/src/@types \
1189
- ${projectName}/src/controllers \
1190
- ${projectName}/src/controllers/utils \
1191
- ${projectName}/src/controllers/utils/messages \
1192
- ${projectName}/src/dto-interfaces \
1193
- ${projectName}/src/models \
1224
+ ${projectName}/src/@types/dto \
1225
+ ${projectName}/src/@types/custom \
1226
+ ${projectName}/src/@types/models \
1227
+ ${projectName}/src/services \
1228
+ ${projectName}/src/services/utils \
1229
+ ${projectName}/src/services/utils/messages \
1230
+ ${projectName}/src/database \
1231
+ ${projectName}/src/database/mongo \
1232
+ ${projectName}/src/database/mongo/models \
1233
+ ${projectName}/src/database/mongo/queries \
1194
1234
  ${projectName}/src/network \
1195
- ${projectName}/src/routes \
1196
- ${projectName}/src/schemas \
1235
+ ${projectName}/src/network/routes \
1236
+ ${projectName}/src/network/routes/schemas \
1197
1237
  ${projectName}/src/test \
1198
1238
  ${projectName}/src/utils
1199
1239
  `)
@@ -1201,53 +1241,97 @@ ${projectName}/src/utils
1201
1241
  // /@types
1202
1242
  await writeFile(data['@types'].index.file, data['@types'].index.content)
1203
1243
 
1204
- // /controllers
1205
- await writeFile(data.controllers.user.file, data.controllers.user.content)
1206
- await writeFile(data.controllers.index.file, data.controllers.index.content)
1244
+ // /@types/custom
1245
+ await writeFile(
1246
+ data['@types/custom'].request.file,
1247
+ data['@types/custom'].request.content
1248
+ )
1249
+ await writeFile(
1250
+ data['@types/custom'].response.file,
1251
+ data['@types/custom'].response.content
1252
+ )
1253
+
1254
+ // /@types/dto
1255
+ await writeFile(data['@types/dto'].user.file, data['@types/dto'].user.content)
1207
1256
 
1208
- // /controllers/utils
1257
+ // /@types/models
1209
1258
  await writeFile(
1210
- data['controllers/utils'].index.file,
1211
- data['controllers/utils'].index.content
1259
+ data['@types/models'].user.file,
1260
+ data['@types/models'].user.content
1212
1261
  )
1213
1262
 
1214
- // /controllers/utils/messages
1263
+ // /services
1264
+ await writeFile(data.services.user.file, data.services.user.content)
1265
+ await writeFile(data.services.index.file, data.services.index.content)
1266
+
1267
+ // /database
1268
+ await writeFile(data.database.index.file, data.database.index.content)
1215
1269
  await writeFile(
1216
- data['controllers/utils/messages'].user.file,
1217
- data['controllers/utils/messages'].user.content
1270
+ data['database/mongo'].index.file,
1271
+ data['database/mongo'].index.content
1218
1272
  )
1219
1273
  await writeFile(
1220
- data['controllers/utils/messages'].index.file,
1221
- data['controllers/utils/messages'].index.content
1274
+ data['database/mongo/models'].index.file,
1275
+ data['database/mongo/models'].index.content
1222
1276
  )
1223
-
1224
- // /dto-interfaces
1225
1277
  await writeFile(
1226
- data['dto-interfaces'].user.file,
1227
- data['dto-interfaces'].user.content
1278
+ data['database/mongo/models'].user.file,
1279
+ data['database/mongo/models'].user.content
1228
1280
  )
1229
1281
  await writeFile(
1230
- data['dto-interfaces'].index.file,
1231
- data['dto-interfaces'].index.content
1282
+ data['database/mongo/queries'].index.file,
1283
+ data['database/mongo/queries'].index.content
1284
+ )
1285
+ await writeFile(
1286
+ data['database/mongo/queries'].user.file,
1287
+ data['database/mongo/queries'].user.content
1232
1288
  )
1233
1289
 
1234
- // /models
1235
- await writeFile(data.models.user.file, data.models.user.content)
1236
- await writeFile(data.models.index.file, data.models.index.content)
1290
+ // /services/utils
1291
+ await writeFile(
1292
+ data['services/utils'].index.file,
1293
+ data['services/utils'].index.content
1294
+ )
1295
+
1296
+ // /services/utils/messages
1297
+ await writeFile(
1298
+ data['services/utils/messages'].user.file,
1299
+ data['services/utils/messages'].user.content
1300
+ )
1301
+ await writeFile(
1302
+ data['services/utils/messages'].index.file,
1303
+ data['services/utils/messages'].index.content
1304
+ )
1237
1305
 
1238
1306
  // /network
1239
- await writeFile(data.network.routes.file, data.network.routes.content)
1240
- await writeFile(data.network.server.file, data.network.server.content)
1241
1307
  await writeFile(data.network.index.file, data.network.index.content)
1308
+ await writeFile(data.network.response.file, data.network.response.content)
1309
+ await writeFile(data.network.router.file, data.network.router.content)
1310
+ await writeFile(data.network.server.file, data.network.server.content)
1242
1311
 
1243
- // /routes
1244
- await writeFile(data.routes.home.file, data.routes.home.content)
1245
- await writeFile(data.routes.user.file, data.routes.user.content)
1246
- await writeFile(data.routes.index.file, data.routes.index.content)
1312
+ // /network/routes
1313
+ await writeFile(
1314
+ data['network/routes'].home.file,
1315
+ data['network/routes'].home.content
1316
+ )
1317
+ await writeFile(
1318
+ data['network/routes'].user.file,
1319
+ data['network/routes'].user.content
1320
+ )
1321
+ await writeFile(
1322
+ data['network/routes'].index.file,
1323
+ data['network/routes'].index.content
1324
+ )
1247
1325
 
1248
- // /schemas
1249
- await writeFile(data.schemas.index.file, data.schemas.index.content)
1250
- await writeFile(data.schemas.user.file, data.schemas.user.content)
1326
+ // /network/routes/schemas
1327
+ await writeFile(
1328
+ data['network/routes/schemas'].index.file,
1329
+ data['network/routes/schemas'].index.content
1330
+ )
1331
+ await writeFile(
1332
+ data['network/routes/schemas'].user.file,
1333
+ data['network/routes/schemas'].user.content
1334
+ )
1251
1335
 
1252
1336
  // /test
1253
1337
  await writeFile(data.test.index.file, data.test.index.content)
@@ -6,7 +6,8 @@ const writeFile = require('../utils/writeFile')
6
6
  */
7
7
  module.exports = async projectName => {
8
8
  const data = {
9
- tsconfigContent: `{
9
+ base: {
10
+ content: `{
10
11
  "ts-node": {
11
12
  "files": true,
12
13
  "require": ["tsconfig-paths/register"]
@@ -25,8 +26,8 @@ module.exports = async projectName => {
25
26
  // "declaration": true, /* Generates corresponding '.d.ts' file. */
26
27
  // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
27
28
  // "outFile": "./", /* Concatenate and emit output to single file. */
28
- "outDir": "./dist/" /* Redirect output structure to the directory. */,
29
- "rootDir": "./src/" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,
29
+ "outDir": "dist" /* Redirect output structure to the directory. */,
30
+ "rootDir": "src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,
30
31
  // "composite": true, /* Enable project compilation */
31
32
  // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
32
33
  // "removeComments": true, /* Do not emit comments to output. */
@@ -53,7 +54,7 @@ module.exports = async projectName => {
53
54
 
54
55
  /* Module Resolution Options */
55
56
  "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
56
- "baseUrl": "./src", /* Base directory to resolve non-absolute module names. */
57
+ "baseUrl": "src", /* Base directory to resolve non-absolute module names. */
57
58
  // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
58
59
  // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
59
60
  // "typeRoots": [], /* List of folders to include type definitions from. */
@@ -79,12 +80,41 @@ module.exports = async projectName => {
79
80
  "skipLibCheck": true /* Skip type checking of declaration files. */,
80
81
  "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
81
82
  },
82
- "include": ["./src/"],
83
- "exclude": ["node_modules", "**/*.test.ts"]
83
+ "include": [
84
+ "src",
85
+ "test",
86
+ "tests",
87
+ "__test__"
88
+ ],
89
+ "exclude": [
90
+ "node_modules"
91
+ ]
84
92
  }
85
93
  `,
86
- tsconfigFile: 'tsconfig.json'
94
+ file: 'tsconfig.base.json'
95
+ },
96
+ prod: {
97
+ content: `{
98
+ "compilerOptions": {
99
+ "rootDir": "src"
100
+ },
101
+ "extends": "./tsconfig.base.json",
102
+ "exclude": [
103
+ "node_modules",
104
+ "test",
105
+ "tests",
106
+ "__test__",
107
+ "dist"
108
+ ],
109
+ "include": [
110
+ "src"
111
+ ]
112
+ }
113
+ `,
114
+ file: 'tsconfig.json'
115
+ }
87
116
  }
88
117
 
89
- await writeFile(`${projectName}/${data.tsconfigFile}`, data.tsconfigContent)
118
+ await writeFile(`${projectName}/${data.base.file}`, data.base.content)
119
+ await writeFile(`${projectName}/${data.prod.file}`, data.prod.content)
90
120
  }
@@ -56,7 +56,7 @@ module.exports = async ({
56
56
  cliProgress.Presets.shades_classic
57
57
  )
58
58
 
59
- const prodPackages = `${manager} express mongoose morgan http-errors joi swagger-ui-express`
59
+ const prodPackages = `${manager} express http-errors joi mongoose morgan swagger-ui-express`
60
60
  const devPackages = `${manager} -D \
61
61
  @types/express \
62
62
  @types/http-errors \
@@ -71,8 +71,6 @@ eslint-config-prettier \
71
71
  eslint-config-standard \
72
72
  eslint-plugin-import \
73
73
  eslint-plugin-prettier \
74
- eslint-plugin-sort-keys-fix \
75
- eslint-plugin-typescript-sort-keys \
76
74
  nodemon \
77
75
  prettier \
78
76
  standard-version \
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anthonylzq/simba.js",
3
- "version": "1.10.0",
3
+ "version": "2.0.0",
4
4
  "description": "set up a modern backend app by running one command",
5
5
  "main": "lib/index.js",
6
6
  "directories": {