on
Italy
- Get link
- X
- Other Apps
src
│ app.js # App entry point
└───api # Express route controllers for all the endpoints of the app
└───config # Environment variables and configuration related stuff
└───jobs # Jobs definitions for agenda.js
└───loaders # Split the startup process into modules
└───models # Database models
└───services # All the business logic is here
└───subscribers # Event handlers for async task
└───types # Type declaration files (d.ts) for Typescript
route.post('/', async (req, res, next) => {
// This should be a middleware or should be handled by a library like Joi.
const userDTO = req.body;
const isUserValid = validators.user(userDTO)
if(!isUserValid) {
return res.status(400).end();
}
// Lot of business logic here...
const userRecord = await UserModel.create(userDTO);
delete userRecord.password;
delete userRecord.salt;
const companyRecord = await CompanyModel.create(userRecord);
const companyDashboard = await CompanyDashboard.create(userRecord, companyRecord);
...whatever...
// And here is the 'optimization' that mess up everything.
// The response is sent to client...
res.json({ user: userRecord, company: companyRecord });
// But code execution continues :(
const salaryRecord = await SalaryModel.create(userRecord, companyRecord);
eventTracker.track('user_signup',userRecord,companyRecord,salaryRecord);
intercom.createUser(userRecord);
gaAnalytics.event('user_signup',userRecord);
await EmailService.startSignupSequence(userRecord)
});
route.post('/',
validators.userSignup, // this middleware take care of validation
async (req, res, next) => {
// The actual responsability of the route layer.
const userDTO = req.body;
// Call to service layer.
// Abstraction on how to access the data layer and the business logic.
const { user, company } = await UserService.Signup(userDTO);
// Return a response to client.
return res.json({ user, company });
});
import UserModel from '../models/user';
import CompanyModel from '../models/company';
export default class UserService() {
async Signup(user) {
const userRecord = await UserModel.create(user);
const companyRecord = await CompanyModel.create(userRecord); // needs userRecord to have the database id
const salaryRecord = await SalaryModel.create(userRecord, companyRecord); // depends on user and company to be created
...whatever
await EmailService.startSignupSequence(userRecord)
...do more stuff
return { user: userRecord, company: companyRecord };
}
}
import UserModel from '../models/user';
import CompanyModel from '../models/company';
import SalaryModel from '../models/salary';
export default class UserService() {
async Signup(user) {
const userRecord = await UserModel.create(user);
const companyRecord = await CompanyModel.create(user);
const salaryRecord = await SalaryModel.create(user, salary);
eventTracker.track(
'user_signup',
userRecord,
companyRecord,
salaryRecord
);
intercom.createUser(
userRecord
);
gaAnalytics.event(
'user_signup',
userRecord
);
await EmailService.startSignupSequence(userRecord)
...more stuff
return { user: userRecord, company: companyRecord };
}
}
import UserModel from '../models/user';
import CompanyModel from '../models/company';
import SalaryModel from '../models/salary';
export default class UserService() {
async Signup(user) {
const userRecord = await this.userModel.create(user);
const companyRecord = await this.companyModel.create(user);
this.eventEmitter.emit('user_signup', { user: userRecord, company: companyRecord })
return userRecord
}
}
eventEmitter.on('user_signup', ({ user, company }) => {
eventTracker.track(
'user_signup',
user,
company,
);
intercom.createUser(
user
);
gaAnalytics.event(
'user_signup',
user
);
})
eventEmitter.on('user_signup', async ({ user, company }) => {
const salaryRecord = await SalaryModel.create(user, company);
})
eventEmitter.on('user_signup', async ({ user, company }) => {
await EmailService.startSignupSequence(user)
})
import UserModel from '../models/user';
import CompanyModel from '../models/company';
import SalaryModel from '../models/salary';
class UserService {
constructor(){}
Sigup(){
// Caling UserMode, CompanyModel, etc
...
}
}
export default class UserService {
constructor(userModel, companyModel, salaryModel){
this.userModel = userModel;
this.companyModel = companyModel;
this.salaryModel = salaryModel;
}
getMyUser(userId){
// models available throug 'this'
const user = this.userModel.findById(userId);
return user;
}
}
import UserService from '../services/user';
import UserModel from '../models/user';
import CompanyModel from '../models/company';
const salaryModelMock = {
calculateNetSalary(){
return 42;
}
}
const userServiceInstance = new UserService(userModel, companyModel, salaryModelMock);
const user = await userServiceInstance.getMyUser('12346');
import { Service } from 'typedi';
@Service()
export default class UserService {
constructor(
private userModel,
private companyModel,
private salaryModel
){}
getMyUser(userId){
const user = this.userModel.findById(userId);
return user;
}
}
import { Container } from 'typedi';
import UserService from '../services/user';
const userServiceInstance = Container.get(UserService);
const user = await userServiceInstance.getMyUser('12346');
route.post('/',
async (req, res, next) => {
const userDTO = req.body;
const userServiceInstance = Container.get(UserService) // Service locator
const { user, company } = userServiceInstance.Signup(userDTO);
return res.json({ user, company });
});
import UserService from '../../../src/services/user';
describe('User service unit tests', () => {
describe('Signup', () => {
test('Should create user record and emit user_signup event', async () => {
const eventEmitterService = {
emit: jest.fn(),
};
const userModel = {
create: (user) => {
return {
...user,
_id: 'mock-user-id'
}
},
};
const companyModel = {
create: (user) => {
return {
owner: user._id,
companyTaxId: '12345',
}
},
};
const userInput= {
fullname: 'User Unit Test',
email: 'test@example.com',
};
const userService = new UserService(userModel, companyModel, eventEmitterService);
const userRecord = await userService.SignUp(teamId.toHexString(), userInput);
expect(userRecord).toBeDefined();
expect(userRecord._id).toBeDefined();
expect(eventEmitterService.emit).toBeCalled();
});
})
})
setTimeout
or another primitive way of delay the execution of code, but on a
framework that persist your jobs, and the execution of them, in a
database..env file, that must never be committed (but it has to exist with default values in your repository) then, the npm package dotenv loads the .env file and insert the vars into the process.env object of node.js.config/index.ts file where the dotenv
npm package and loads the .env file and then I use an object to store
the variables, so we have a structure and code autocompletion.const dotenv = require('dotenv');
// config() will read your .env file, parse the contents, assign it to process.env.
dotenv.config();
export default {
port: process.env.PORT,
databaseURL: process.env.DATABASE_URI,
paypal: {
publicKey: process.env.PAYPAL_PUBLIC_KEY,
secretKey: process.env.PAYPAL_SECRET_KEY,
},
paypal: {
publicKey: process.env.PAYPAL_PUBLIC_KEY,
secretKey: process.env.PAYPAL_SECRET_KEY,
},
mailchimp: {
apiKey: process.env.MAILCHIMP_API_KEY,
sender: process.env.MAILCHIMP_SENDER,
}
}
process.env.MY_RANDOM_VAR instructions, and by having the autocompletion you don’t have to know how to name the env var.const mongoose = require('mongoose');
const express = require('express');
const bodyParser = require('body-parser');
const session = require('express-session');
const cors = require('cors');
const errorhandler = require('errorhandler');
const app = express();
app.get('/status', (req, res) => { res.status(200).end(); });
app.head('/status', (req, res) => { res.status(200).end(); });
app.use(cors());
app.use(require('morgan')('dev'));
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json(setupForStripeWebhooks));
app.use(require('method-override')());
app.use(express.static(__dirname + '/public'));
app.use(session({ secret: process.env.SECRET, cookie: { maxAge: 60000 }, resave: false, saveUninitialized: false }));
mongoose.connect(process.env.DATABASE_URL, { useNewUrlParser: true });
require('./config/passport');
require('./models/user');
require('./models/company');
app.use(require('./routes'));
app.use((req, res, next) => {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
app.use((err, req, res) => {
res.status(err.status || 500);
res.json({'errors': {
message: err.message,
error: {}
}});
});
... more stuff
... maybe start up Redis
... maybe add more middlewares
async function startServer() {
app.listen(process.env.PORT, err => {
if (err) {
console.log(err);
return;
}
console.log(`Your server is ready !`);
});
}
// Run the async function to start our server
startServer();
const loaders = require('./loaders');
const express = require('express');
async function startServer() {
const app = express();
await loaders.init({ expressApp: app });
app.listen(process.env.PORT, err => {
if (err) {
console.log(err);
return;
}
console.log(`Your server is ready !`);
});
}
startServer();
import expressLoader from './express';
import mongooseLoader from './mongoose';
export default async ({ expressApp }) => {
const mongoConnection = await mongooseLoader();
console.log('MongoDB Intialized');
await expressLoader({ app: expressApp });
console.log('Express Intialized');
// ... more loaders can be here
// ... Initialize agenda
// ... or Redis, or whatever you want
}
import * as express from 'express';
import * as bodyParser from 'body-parser';
import * as cors from 'cors';
export default async ({ app }: { app: express.Application }) => {
app.get('/status', (req, res) => { res.status(200).end(); });
app.head('/status', (req, res) => { res.status(200).end(); });
app.enable('trust proxy');
app.use(cors());
app.use(require('morgan')('dev'));
app.use(bodyParser.urlencoded({ extended: false }));
// ...More middlewares
// Return the express app
return app;
})
import * as mongoose from 'mongoose'
export default async (): Promise<any> => {
const connection = await mongoose.connect(process.env.DATABASE_URL, { useNewUrlParser: true });
return connection.connection.db;
}
Comments
Post a Comment