UNPKG

easy-express-cwa

Version:

CLI tool to setup a common Express.js backend developed by codewithashim

454 lines (377 loc) 12.3 kB
# Easy Express CWA 🚀 `easy-express` is a CLI tool to automate the setup of a common Express.js backend. This tool helps you quickly scaffold a backend setup without rewriting the setup each time. ## Features - **Predefined Backend Setup**: Copies a predefined backend setup to your current working directory. - **Common Configurations**: Includes configurations such as `.env.example`, `.eslintignore`, `.eslintrc`, `.gitignore`, `.prettierrc`, `docker-compose.yml`, `Dockerfile`, `package.json`, and `tsconfig.json`. - **Prebuilt Functionalities**: * 🔐 Authentication and authorization * 🔑 JWT handling * 🔗 Login with Google Auth * 📜 Logger setup * 🐳 Docker configuration * 🚀 Redis integration * 📂 File upload middleware (Cloudinary, AWS S3) * 📘 Swagger for API documentation * 🍪 Cookies handling * 🔒 Security features * 🛠️ Many more features upcoming ## Installation 🛠 ```sh npm install -g easy-express-cwa mkdir server ``` ```sh cd server ``` ```sh npx easy-express-cwa ``` Configure your environment variables: Copy the `.env.example` file to `.env` and update the values as needed: ```sh NODE_ENV=development PORT=8000 DB_URL=mongodb://localhost:27017/yourdb ENCRYPTION_METHOD=AES-256-CBC ENCRYPTION_KEY=DR8j97BtgHVBiEKAjqRlfn6VSLTJTIpwsgNo0vTWKvA= BCRYPT_SALT_ROUNDS=14 DOMAIN=yourdomain.com APP_ID=your-app-id APP_CERTIFICATE=your-app-certificate JWT_SECRET=GiCj9Qrmy4vYeDbBjrVCszy0xlN5PGZQQ77iLExHVuI= JWT_REFRESH_SECRET=jiS8zP3qHU2fgKblrhqVKhFEYYqpwsrh/6Z/Ak0ZhL8= JWT_EXPIRATION_TIME=3d JWT_REFRESH_EXPIRATION_TIME=3d CLOUDINARY_CLOUD_NAME="" CLOUDINARY_API_KEY="" CLOUDINARY_API_SECRET="" REDIS_PASSWORD=your-redis-password REDIS_HOST=your-redis-host REDIS_PORT=your-redis-port GOOGLE_CLIENT_ID=your-google-client-id GOOGLE_CLIENT_SECRET=your-google-client-secret GOOGLE_CALLBACK_URL=your-google-callback-url GOOGLE_REDIRECT_URL=your-google-redirect-url GOOGLE_APP_USER=your-google-app-user GOOGLE_APP_PASSWORD=your-google-app-password ``` # Folder Structure 📂 Here's the folder structure generated by `easy-express`: ``` └── 📁 server └── 🚫 .dockerignore └── 🛠️ .env └── 🛠️ .env.example └── 🐳 Dockerfile └── 📜 README.md └── 🐳 docker-compose.yml └── 📦 package.json └── 📁 src └── 📁 app └── 📁 middlewares └── 🔒 auth.ts └── 📁 cloudinary └── ☁️ cloudinary.ts └── ⚠️ globalErrorHandler.ts └── 🛠️ handleZodError.ts └── 📁 multer └── 📤 multer.ts └── 📁 redis └── 🛠️ redis.ts └── validateRequest.ts └── 📁 modules └── 📁 auth └── 👤 auth.controller.ts └── 🚦 auth.route.ts └── 🛠️ auth.service.ts └── 📁 example └── 📄 example.controller.ts └── 📄 example.interface.ts └── 🗃️ example.model.ts └── 🚦 example.route.ts └── 🛠️ example.service.ts └── example.validation.ts └── 📁 googleOAuth └── 🌐 googleOAuth.controller.ts └── 🚦 googleOAuth.route.ts └── 🛠️ googleOAuth.service.ts └── 📁 user └── 👤 user.controller.ts └── 🗃️ user.interface.ts └── 🗃️ user.model.ts └── 🚦 user.route.ts └── 🛠️ user.service.ts └── user.validation.ts └── 📁 routes └── 🚦 index.ts └── 🛠️ app.ts └── 📁 config └── ⚙️ index.ts └── 🛂 passport.ts └── 📁 constants └── 💬 message.ts └── 🔢 pagination.ts └── redisCacheExpireDate.ts └── 🔑 redisKeys.ts └── 📁 enums └── 📄 common.ts └── 📄 user.ts └── 📁 errors └── 🛠️ ApiError.ts └── handleCastError.ts └── 🛠️ handleValidationError.ts └── 🛠️ handleZodError.ts └── 📁 helpers └── 🛡️ jwtHelper.ts └── 🛠️ paginationHelper.ts └── 📁 interfaces └── 📄 common.ts └── 📄 error.ts └── 📄 index.d.ts └── 📄 pagination.ts └── 🛠️ server.ts └── 📁 shared └── 🛠️ catchAsync.ts └── 📋 logger.ts └── 🛠️ pick.ts └── ✉️ sendResponse.ts └── 📁 utils └── 📧 mail.util.ts └── 🔑 oAuth.ts └── ⚙️ tsconfig.json └── 📦 yarn.lock ``` ### API Documentation Access the Swagger API documentation at `http://localhost:3000/api-docs`. ## Contributing 🤝 Contributions are welcome! Please open an issue or submit a pull request for any improvements. ## License 📄 This project is licensed under the MIT License. ## Contact 📬 For any inquiries or feedback, please reach out at [codewithashim@gmail.com](). # **Example** To add the example code snippets for the `example` entity CRUD operations in your `README.md` file, you can follow this structure: 1. **Overview of the Example Module** 2. **Interface** 3. **Model** 4. **Controller** 5. **Service** 6. **Route** 7. **Validation** Here’s how you can add them to your `README.md`: # Example Module This module provides CRUD operations for the `example` entity. Below are the code snippets for the controller, interface, model, route, service, and validation. ## Interface ```typescript import { Model } from "mongoose"; export type IExample = { title: string; description: string; createdAt?: Date; updatedAt?: Date; } export type ExampleModel =Model<IExample, Record<string, unknown>>; ``` ## Model ```typescript import { Schema, model } from "mongoose"; import { IExample, ExampleModel } from "./example.interface"; const ExampleSchema = new Schema<IExample>( { name: { type: String, required: true, }, description: { type: String, required: true, }, }, { timestamps: true, toJSON: { virtuals: true, }, } ); export const Example = model<IExample, ExampleModel>("Example", ExampleSchema); ``` ## Controller ```typescript import { Request, Response } from "express"; import httpStatus from "http-status"; import catchAsync from "../../../shared/catchAsync"; import sendResponse from "../../../shared/sendResponse"; import { IExample } from "./example.interface"; import { ExampleService } from "./example.service"; import { responseMessage } from "../../../constants/message"; const getAllExamples = catchAsync(async (req: Request, res: Response) => { const result = await ExampleService.getAllExamples(); sendResponse<IExample[]>(res, { statusCode: httpStatus.OK, success: true, message: responseMessage.GET_ALL_EXAMPLES_MESSAGE, data: result, }); }); const getExampleById = catchAsync(async (req: Request, res: Response) => { const result = await ExampleService.getExampleById(req.params.id); sendResponse<IExample>(res, { statusCode: httpStatus.OK, success: true, message: responseMessage.GET_EXAMPLE_BY_ID_MESSAGE, data: result, }); }); const updateExample = catchAsync(async (req: Request, res: Response) => { const id = req.params.id; const updatedData = req.body; const result = await ExampleService.updateExample(id, updatedData); sendResponse<IExample>(res, { statusCode: httpStatus.OK, success: true, message: responseMessage.UPDATE_EXAMPLE_MESSAGE, data: result, }); }); const deleteExample = catchAsync(async (req: Request, res: Response) => { const { id } = req.params; const result = await ExampleService.deleteExample(id); sendResponse<IExample>(res, { statusCode: httpStatus.OK, success: true, message: responseMessage.DELETE_EXAMPLE_MESSAGE, data: result, }); }); export const ExampleController = { getAllExamples, getExampleById, updateExample, deleteExample, }; ``` ## Service ```typescript import ApiError from "../../../errors/ApiError"; import { Example } from "./example.model"; import { IExample } from "./example.interface"; import httpStatus from "http-status"; import { responseMessage } from "../../../constants/message"; const getAllExamples = async (): Promise<Array<IExample>> => { try { const examples = await Example.find(); return examples; } catch (error) { throw new ApiError( httpStatus.INTERNAL_SERVER_ERROR, `${responseMessage.FAILED_MESSAGE} get all examples` ); } }; const getExampleById = async (id: string): Promise<IExample | null> => { try { const example = await Example.findById(id); if (!example) { throw new ApiError( httpStatus.NOT_FOUND, `Example ${responseMessage.NOT_FOUND_MESSAGE}` ); } return example; } catch (error) { throw new ApiError( httpStatus.INTERNAL_SERVER_ERROR, `${responseMessage.FAILED_MESSAGE} get example by ID` ); } }; const updateExample = async ( id: string, payload: Partial<IExample> ): Promise<IExample | null> => { try { const isExist = await Example.findOne({ _id: id }); if (!isExist) { throw new ApiError( httpStatus.NOT_FOUND, `Example ${responseMessage.NOT_FOUND_MESSAGE}` ); } const updateExampleData = payload; const result = await Example.findOneAndUpdate({ _id: id }, updateExampleData, { new: true, }); return result; } catch (error) { throw new ApiError( httpStatus.INTERNAL_SERVER_ERROR, `${responseMessage.FAILED_MESSAGE} update example` ); } }; const deleteExample = async (id: string): Promise<IExample | null> => { try { const example = await Example.findByIdAndDelete(id); if (!example) { throw new ApiError( httpStatus.NOT_FOUND, `Example ${responseMessage.NOT_FOUND_MESSAGE}` ); } return example; } catch (error) { throw new ApiError( httpStatus.INTERNAL_SERVER_ERROR, `${responseMessage.FAILED_MESSAGE} delete example` ); } }; export const ExampleService = { getAllExamples, getExampleById, updateExample, deleteExample, }; ``` ## Route ```typescript import express from "express"; import validateRequest from "../../middlewares/validateRequest"; import { ExampleController } from "./example.controller"; import { createExampleValidator } from "./example.validation"; const router = express.Router(); router.get("/", ExampleController.getAllExamples); router.get("/:id", ExampleController.getExampleById); router.patch( "/:id", validateRequest(createExampleValidator.updateExampleZodSchema), ExampleController.updateExample ); router.delete("/:id", ExampleController.deleteExample); export const ExampleRoutes = router ``` ## Validation ```typescript import { z } from "zod"; const createExampleZodSchema = z.object({ body: z.object({ name: z.string({ required_error: "Name is required", }), description: z.string({ required_error: "Description is required", }), }), }); const updateExampleZodSchema = z.object({ body: z.object({ name: z.string().optional(), description: z.string().optional(), }), }); export const createExampleValidator = { createExampleZodSchema, updateExampleZodSchema, }; ```