Skip to content

Commit 19c454b

Browse files
committed
first commit
0 parents  commit 19c454b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+6038
-0
lines changed

.eslintignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules
2+
dist

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
node_modules
2+
.env
3+
dist
4+
5+
/winston
6+
/uploads

.prettierrc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"semi": true,
3+
"singleQuote": true,
4+
"arrowParens": "avoid"
5+
}

README.md

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# Project Name
2+
3+
This is a template project for backend development using Typescript, Node.js, Express, Mongoose, Bcrypt, JWT, NodeMailer, Multer, ESLint, and Prettier. The aim is to reduce setup time for new backend projects.
4+
5+
## Features
6+
7+
- **Authentication API:** Complete authentication system using JWT for secure token-based authentication and bcrypt for password hashing.
8+
- **File Upload:** Implemented using Multer with efficient file handling and short-term storage.
9+
- **Data Validation:** Robust data validation using Zod and Mongoose schemas.
10+
- **Code Quality:** Ensured code readability and quality with ESLint and Prettier.
11+
- **Email Service:** Sending emails through NodeMailer.
12+
- **File Handling:** Efficient file deletion using `fs.unlink`.
13+
- **Environment Configuration:** Easy configuration using a `.env` file.
14+
- **Logging:** Logging with Winston and file rotation using DailyRotateFile.
15+
- **API Request Logging:** Logging API requests using Morgan.
16+
17+
## Tech Stack
18+
19+
- Typescript
20+
- Node.js
21+
- Express
22+
- Mongoose
23+
- Bcrypt
24+
- JWT
25+
- NodeMailer
26+
- Multer
27+
- ESLint
28+
- Prettier
29+
- Winston
30+
- Daily-winston-rotate-file
31+
- Morgen
32+
- Socket
33+
34+
## Getting Started
35+
36+
Follow these steps to set up and run the project locally.
37+
38+
### Prerequisites
39+
40+
Ensure you have the following installed:
41+
42+
- Node.js
43+
- npm or yarn
44+
45+
### Installation
46+
47+
1. **Clone the repository:**
48+
49+
```bash
50+
git clone https://github.com/yourusername/your-repository.git
51+
cd your-repository
52+
```
53+
54+
2. **Install dependencies:**
55+
56+
Using npm:
57+
58+
```bash
59+
npm install
60+
```
61+
62+
Using yarn:
63+
64+
```bash
65+
yarn install
66+
```
67+
68+
3. **Create a `.env` file:**
69+
70+
In the root directory of the project, create a `.env` file and add the following variables. Adjust the values according to your setup.
71+
72+
```env
73+
# Basic
74+
NODE_ENV=development
75+
DATABASE_URL=mongodb://127.0.0.1:27017/project_name
76+
IP_ADDRESS=192.0.0.0
77+
PORT=5000
78+
79+
# Bcrypt
80+
BCRYPT_SALT_ROUNDS=12
81+
82+
# JWT
83+
JWT_SECRET=jwt_secret
84+
JWT_EXPIRE_IN=1d
85+
86+
# Email
87+
EMAIL_FROM=email@gmail.com
88+
EMAIL_USER=email@gmail.com
89+
EMAIL_PASS=mkqcfjeqloothyax
90+
EMAIL_PORT=587
91+
EMAIL_HOST=smtp.gmail.com
92+
```
93+
94+
4. **Run the project:**
95+
96+
Using npm:
97+
98+
```bash
99+
npm run dev
100+
```
101+
102+
Using yarn:
103+
104+
```bash
105+
yarn run dev
106+
```
107+
108+
### Running the Tests
109+
110+
Explain how to run the automated tests for this system.
111+
112+
```bash
113+
npm test
114+
```

package.json

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
{
2+
"name": "backend-template-typescript-mongoose-express",
3+
"version": "1.0.0",
4+
"description": "",
5+
"main": "index.js",
6+
"scripts": {
7+
"dev": "ts-node-dev --respawn --transpile-only src/server.ts",
8+
"start": "node dist/server.js",
9+
"build": "tsc",
10+
"lint:check": "eslint --ignore-path .eslintignore --ext .js,.ts",
11+
"lint:fix": "lint . --fix",
12+
"prettier:check": "prettier --ignore-path .gitignore --write \"**/*.+(js|ts|json)\"",
13+
"prettier:fix": "prettier --write .",
14+
"test": "echo \"Error: no test specified\" && exit 1"
15+
},
16+
"keywords": [],
17+
"author": "Rahad-Ullah",
18+
"license": "ISC",
19+
"devDependencies": {
20+
"@types/bcrypt": "^5.0.2",
21+
"@types/bcryptjs": "^2.4.6",
22+
"@types/cors": "^2.8.17",
23+
"@types/express": "^4.17.21",
24+
"@types/jsonwebtoken": "^9.0.6",
25+
"@types/morgan": "^1.9.9",
26+
"@types/multer": "^1.4.11",
27+
"@types/nodemailer": "^6.4.15",
28+
"@typescript-eslint/eslint-plugin": "^7.15.0",
29+
"@typescript-eslint/parser": "^7.15.0",
30+
"eslint": "^8.56.0",
31+
"eslint-config-prettier": "^9.1.0",
32+
"ts-node-dev": "^2.0.0",
33+
"typescript": "^5.5.3"
34+
},
35+
"dependencies": {
36+
"bcrypt": "^5.1.1",
37+
"colors": "^1.4.0",
38+
"cors": "^2.8.5",
39+
"dotenv": "^16.4.5",
40+
"express": "^4.19.2",
41+
"http-status-codes": "^2.3.0",
42+
"i": "^0.3.7",
43+
"jsonwebtoken": "^9.0.2",
44+
"mongoose": "^8.4.4",
45+
"morgan": "^1.10.0",
46+
"multer": "^1.4.5-lts.1",
47+
"nodemailer": "^6.9.14",
48+
"npm": "^10.8.1",
49+
"socket.io": "^4.7.5",
50+
"winston": "^3.13.0",
51+
"winston-daily-rotate-file": "^5.0.0",
52+
"zod": "^3.23.8"
53+
}
54+
}

src/DB/seedAdmin.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { User } from '../app/modules/user/user.model';
2+
import config from '../config';
3+
import { USER_ROLES } from '../enums/user';
4+
import { logger } from '../shared/logger';
5+
6+
const payload = {
7+
name: 'Administrator',
8+
email: config.super_admin.email,
9+
role: USER_ROLES.SUPER_ADMIN,
10+
password: config.super_admin.password,
11+
verified: true,
12+
};
13+
14+
export const seedSuperAdmin = async () => {
15+
const isExistSuperAdmin = await User.findOne({
16+
email: config.super_admin.email,
17+
role: USER_ROLES.SUPER_ADMIN,
18+
});
19+
if (!isExistSuperAdmin) {
20+
await User.create(payload);
21+
logger.info('✨ Super Admin account has been successfully created!');
22+
}
23+
};

src/app.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import cors from 'cors';
2+
import express, { Request, Response } from 'express';
3+
import { StatusCodes } from 'http-status-codes';
4+
import globalErrorHandler from './app/middlewares/globalErrorHandler';
5+
import router from './routes';
6+
import { Morgan } from './shared/morgen';
7+
const app = express();
8+
9+
//morgan
10+
app.use(Morgan.successHandler);
11+
app.use(Morgan.errorHandler);
12+
13+
//body parser
14+
app.use(cors());
15+
app.use(express.json());
16+
app.use(express.urlencoded({ extended: true }));
17+
18+
//file retrieve
19+
app.use(express.static('uploads'));
20+
21+
//router
22+
app.use('/api/v1', router);
23+
24+
//live response
25+
app.get('/', (req: Request, res: Response) => {
26+
const date = new Date(Date.now());
27+
res.send(
28+
`<h1 style="text-align:center; color:#173616; font-family:Verdana;">Beep-beep! The server is alive and kicking.</h1>
29+
<p style="text-align:center; color:#173616; font-family:Verdana;">${date}</p>
30+
`
31+
);
32+
});
33+
34+
//global error handle
35+
app.use(globalErrorHandler);
36+
37+
//handle not found route;
38+
app.use((req, res) => {
39+
res.status(StatusCodes.NOT_FOUND).json({
40+
success: false,
41+
message: 'Not found',
42+
errorMessages: [
43+
{
44+
path: req.originalUrl,
45+
message: "API DOESN'T EXIST",
46+
},
47+
],
48+
});
49+
});
50+
51+
export default app;

src/app/builder/QueryBuilder.ts

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { FilterQuery, Query } from 'mongoose';
2+
3+
class QueryBuilder<T> {
4+
public modelQuery: Query<T[], T>;
5+
public query: Record<string, unknown>;
6+
7+
constructor(modelQuery: Query<T[], T>, query: Record<string, unknown>) {
8+
this.modelQuery = modelQuery;
9+
this.query = query;
10+
}
11+
12+
//searching
13+
search(searchableFields: string[]) {
14+
if (this?.query?.searchTerm) {
15+
this.modelQuery = this.modelQuery.find({
16+
$or: searchableFields.map(
17+
field =>
18+
({
19+
[field]: {
20+
$regex: this.query.searchTerm,
21+
$options: 'i',
22+
},
23+
} as FilterQuery<T>)
24+
),
25+
});
26+
}
27+
return this;
28+
}
29+
30+
//filtering
31+
filter() {
32+
const queryObj = { ...this.query };
33+
const excludeFields = ['searchTerm', 'sort', 'page', 'limit', 'fields'];
34+
excludeFields.forEach(el => delete queryObj[el]);
35+
36+
this.modelQuery = this.modelQuery.find(queryObj as FilterQuery<T>);
37+
return this;
38+
}
39+
40+
//sorting
41+
sort() {
42+
let sort = (this?.query?.sort as string) || '-createdAt';
43+
this.modelQuery = this.modelQuery.sort(sort);
44+
45+
return this;
46+
}
47+
48+
//pagination
49+
paginate() {
50+
let limit = Number(this?.query?.limit) || 10;
51+
let page = Number(this?.query?.page) || 1;
52+
let skip = (page - 1) * limit;
53+
54+
this.modelQuery = this.modelQuery.skip(skip).limit(limit);
55+
56+
return this;
57+
}
58+
59+
//fields filtering
60+
fields() {
61+
let fields =
62+
(this?.query?.fields as string)?.split(',').join(' ') || '-__v';
63+
this.modelQuery = this.modelQuery.select(fields);
64+
65+
return this;
66+
}
67+
68+
//populating
69+
populate(populateFields: string[], selectFields: Record<string, unknown>) {
70+
this.modelQuery = this.modelQuery.populate(
71+
populateFields.map(field => ({
72+
path: field,
73+
select: selectFields[field],
74+
}))
75+
);
76+
return this;
77+
}
78+
79+
//pagination information
80+
async getPaginationInfo() {
81+
const total = await this.modelQuery.model.countDocuments(
82+
this.modelQuery.getFilter()
83+
);
84+
const limit = Number(this?.query?.limit) || 10;
85+
const page = Number(this?.query?.page) || 1;
86+
const totalPage = Math.ceil(total / limit);
87+
88+
return {
89+
total,
90+
limit,
91+
page,
92+
totalPage,
93+
};
94+
}
95+
}
96+
97+
export default QueryBuilder;

0 commit comments

Comments
 (0)