-
-
Notifications
You must be signed in to change notification settings - Fork 1
Home
NodeSeQ is a boilerplate for building Node.js apps using TypeScript and the Sequelize ORM.
- Quick start
- Continuous Integration
- Documentation Standards
- Test Coverage Maintenance
- Production-Ready Setup
- To run this app on a local machine, make sure the PostgreSQL database is set up and running. If not, you can use Docker and execute the following command to run the database: npm run db:up
- Clone the Application
git clone https://github.com/santoshshinde2012/node-ts-sequelize-pg-boilerplate.git
- Install the dependencies
npm install
- Start the application
npm run dev
- To run the test cases
npm run test
- Need to develop node js an app that offers common functionality for node js apps.
- Create REST API
-
Entity
- Enquiry
-
Mapping
-
ER Diagram
-
Enquiry: An Enquiry is a master entity that has a set of properties.
- GET
/v1/enquiries
Get a list of all enquiries. - POST
/v1/enquiries
Create a new Enquiry. - GET
/v1/enquiries/{id}
Get details of a specific Enquiry. - PUT
/v1/enquiries/{id}
Update details of a specific Enquiry. - DELETE
/v1/enquiries/{id}
Delete a specific Enquiry.
- GET
- Database
- Created a Models folder to place the database model, and it is based on sequelize.
- Extracted config from the .env variable and exported from the
index.ts
file
- Components
Components are divided into two parts:
Controller
andService
and The service is responsible for communicating with the respective database model. - Routes
- Register the controller and its register method for REST endpoints.
- Common Services
- This service is accountable for performing common operations between multiple database models.
- We can inject this into the component service and access its method in the controller from the component service.
- Unit Test Cases
- Created unit test cases using Jest with 90+ code coverage.
- Tried to cover positive and negative scenerio
- Code Coverage Report
- Sonar Cloud Scan
If you are using a Docker-based database, use the commands below to set up the database. Make sure to change the variables in the.env file if you are using a local or remote database.
"db:up": "docker-compose -f docker/postgresql/docker-compose.yml up",
"db:down": "docker-compose -f docker/postgresql/docker-compose.yml down"
Define your models
in the src/database/models
folder; in our case, we are going to create a sample model, Enquiry.ts
.
import { DataTypes, Model, Optional, UUIDV4 } from 'sequelize';
import sequelize from '../index';
interface EnquiryAttributes {
id: string;
name: string;
subject: string;
body: string;
email: string;
country: string;
}
interface EnquiryCreationAttributes
extends Optional<EnquiryAttributes, 'id'> {}
class Enquiry
extends Model<EnquiryAttributes, EnquiryCreationAttributes>
implements EnquiryAttributes
{
public id!: string;
public name!: string;
public country!: string;
public subject!: string;
public body!: string;
public email!: string;
public readonly createdAt!: Date;
public readonly updatedAt!: Date;
}
Enquiry.init(
{
id: {
type: DataTypes.UUID,
defaultValue: UUIDV4,
primaryKey: true,
},
name: {
type: DataTypes.STRING(100),
allowNull: false,
},
country: {
type: DataTypes.STRING(100),
allowNull: false,
},
subject: {
type: DataTypes.STRING(200),
allowNull: false,
},
email: {
type: DataTypes.STRING(100),
allowNull: false,
},
body: {
type: DataTypes.STRING(400),
allowNull: false,
}
},
{
sequelize,
modelName: 'Enquiry',
tableName: 'Enquiry',
timestamps: true,
},
);
export { Enquiry, EnquiryAttributes, EnquiryCreationAttributes };
- Components Components are divided into two parts: Controller and Service and The service is responsible for communicating with the respective database model.
- Routes - Register the controller and its register method for REST endpoints.
- Common Services - This service is accountable for performing common operations between multiple database models. We can inject this into the component service and access its method in the controller from the component service.
Create src/components/enquiry/EnquiryService.ts
file to handle database operation for created model using sequelize.
import {
Enquiry,
EnquiryAttributes,
EnquiryCreationAttributes,
} from '../../database/models/Enquiry';
import logger from '../../lib/logger';
import ApiError from '../../abstractions/ApiError';
import { StatusCodes } from 'http-status-codes';
export class EnquiryService {
async getAll(): Promise<EnquiryAttributes[]> {
try {
const enquiries = await Enquiry.findAll();
return enquiries;
} catch (error) {
logger.error(error);
throw error;
}
}
async getById(id: string | number): Promise<EnquiryAttributes> {
try {
const enquiry = await Enquiry.findByPk(id);
if (!enquiry) {
throw new ApiError('Enquiry not found', StatusCodes.NOT_FOUND);
}
return enquiry;
} catch (error) {
logger.error(error);
throw error;
}
}
async update(
id: string | number,
payload: Partial<EnquiryCreationAttributes>,
): Promise<EnquiryAttributes> {
try {
const enquiry = await Enquiry.findByPk(id);
if (!enquiry) {
throw new ApiError(
'Enquiry not found',
StatusCodes.NOT_FOUND,
);
}
const updatedEnquiry = await enquiry.update(payload);
return updatedEnquiry;
} catch (error) {
logger.error(error);
throw error;
}
}
async create(
payload: EnquiryCreationAttributes,
): Promise<EnquiryAttributes> {
try {
const enquiry = await Enquiry.create(payload);
return enquiry;
} catch (error) {
logger.error(error);
throw error;
}
}
async delete(id: string | number): Promise<boolean> {
try {
const deletedEnquiryCount = await Enquiry.destroy({
where: { id },
});
return !!deletedEnquiryCount;
} catch (error) {
logger.error(error);
throw error;
}
}
}
Create src/components/enquiry/EnquiryController.ts
file to handle controller part of REST API.
import { NextFunction, Request, Response, Router } from 'express';
import { ReasonPhrases, StatusCodes } from 'http-status-codes';
import BaseApi from '../BaseApi';
import { EnquiryService } from './EnquiryService';
import { EnquiryAttributes } from '../../database/models/Enquiry';
import ApiError from '../../abstractions/ApiError';
/**
* Enquiry controller
*/
export default class EnquiryController extends BaseApi {
private enquiry: EnquiryService;
public basePath: string = 'enquiries';
constructor() {
super();
this.enquiry = new EnquiryService();
}
/**
*
*/
public register(): Router {
this.router.get('/', this.getEnquiries.bind(this));
this.router.get('/:id', this.getEnquiry.bind(this));
this.router.post('/', this.createEnquiry.bind(this));
this.router.put('/:id', this.updateEnquiry.bind(this));
this.router.delete('/:id', this.delete.bind(this));
return this.router;
}
/**
*
* @param req
* @param res
* @param next
*/
public async getEnquiries(
req: Request,
res: Response,
next: NextFunction,
): Promise<void> {
try {
const enquiries: EnquiryAttributes[] =
await this.enquiry.getAll();
res.locals.data = enquiries;
// call base class method
this.send(res);
} catch (err) {
next(err);
}
}
/**
*
* @param req
* @param res
* @param next
*/
public async getEnquiry(
req: Request,
res: Response,
next: NextFunction,
): Promise<void> {
try {
const id = req.params.id;
const enquiry: EnquiryAttributes =
await this.enquiry.getById(id);
res.locals.data = enquiry;
// call base class method
this.send(res);
} catch (err) {
next(err);
}
}
/**
*
* @param req
* @param res
* @param next
*/
public async updateEnquiry(
req: Request,
res: Response,
next: NextFunction,
): Promise<void> {
try {
const id = req.params.id;
const { body } = req;
const enquiry: EnquiryAttributes =
await this.enquiry.update(id, body);
res.locals.data = {
enquiry,
};
// call base class method
this.send(res);
} catch (err) {
next(err);
}
}
/**
*
* @param req
* @param res
* @param next
*/
public async createEnquiry(
req: Request,
res: Response,
next: NextFunction,
): Promise<void> {
try {
const { name, country, subject, body, email } = req.body;
if (!name && !country) {
throw new ApiError(
ReasonPhrases.BAD_REQUEST,
StatusCodes.BAD_REQUEST,
);
}
const enquiry: EnquiryAttributes =
await this.enquiry.create({ name, country, subject, body, email });
res.locals.data = {
enquiry,
};
// call base class method
super.send(res, StatusCodes.CREATED);
} catch (err) {
next(err);
}
}
/**
*
* @param req
* @param res
* @param next
*/
public async delete(
req: Request,
res: Response,
next: NextFunction,
): Promise<void> {
try {
const id = req.params.id;
const status: boolean = await this.enquiry.delete(id);
res.locals.data = {
status,
};
// call base class method
this.send(res);
} catch (err) {
next(err);
}
}
}
We need to register the routes that we defined earlier; for that, we have to make an entry in src/routes.ts
.
import { Router } from 'express';
import EnquiryController from './components/enquiry/EnquiryController';
/**
* Here, you can register routes by instantiating the controller.
*
*/
export default function registerRoutes(): Router {
const router = Router();
// Define an array of controller objects
const controllers = [
new EnquiryController(),
];
// Dynamically register routes for each controller
controllers.forEach((controller) => {
// make sure each controller has basePath attribute and register() method
router.use(`/v1/${controller.basePath}`, controller.register());
});
return router;
}