santiq / bulletproof-nodejs Goto Github PK
View Code? Open in Web Editor NEWImplementation of a bulletproof node.js API ๐ก๏ธ
Home Page: https://softwareontheroad.com/ideal-nodejs-project-structure/
License: MIT License
Implementation of a bulletproof node.js API ๐ก๏ธ
Home Page: https://softwareontheroad.com/ideal-nodejs-project-structure/
License: MIT License
src/subscribers/user.ts:29:14 - error TS2339: Property 'error' does not exist on type 'unknown'
import { Container } from 'typedi';
const Logger = Container.get('logger');
Logger.error(`๐ฅ Error on event ${events.user.signIn}: %o`, e);
But if I do the next code - everything is fine
import Logger from '../loaders/logger';
Logger.error(`๐ฅ Error on event ${events.user.signIn}: %o`, e);
As I can see. typedi do not see levels in Container.
How can I fix this ?
According to Agenda.js docs, agenda.start() is called using await, along with other functions like every, schedule,...
Since on index loader agenda is being called with an await, should'nt it be an async function?
My guess is that injecting dependencies is more important when they own resources, but I'm still curious why something like config isn't injected.
Hi, i found that call save() not work when I'd like to save data to database.
code as follows:
const userRecord = await this.userModel.create({
...userDTO,
salt,
password: hashedPwd,
});
// save to database
await userRecord.save();
endpoint api works well, and return
{
"user": {
"role": "user",
"_id": "5ed08b923de44903c4ad5320",
"name": "admin",
"email": "404398jiwerewtwr34235kjgelg",
"createdAt": "2020-05-29T04:12:02.662Z",
"updatedAt": "2020-05-29T04:12:02.662Z",
"__v": 0
},
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI1ZWQwOGI5MjNkZTQ0OTAzYzRhZDUzMjAiLCJyb2xlIjoidXNlciIsIm5hbWUiOiJhZG1pbiIsImV4cCI6MTU5MDcyNTUyMi42NzYsImlhdCI6MTU5MDcyNTUyMn0.oy4IP3sZ6sga12-yjXZN9sr03fx4V3c-KFWF_Jq_OWM"
}
but in my mongodb docker container,
I search users by users.find({})
,
error comes out as follows:
2020-05-29T04:07:49.242+0000 E QUERY [js] uncaught exception: ReferenceError: users is not defined :
Any help will be appreciated.
BTW, this architecture is good, but seems not suitable for small project. :(
It takes several hours for me to figure out how it works.
Thanks
Hello , I am facing issue for getting injected model instance in test case
here is the detail
I have test file called
inventory.service.test.ts
import { Container } from 'typedi';
import InventoryService from '../../../src/services/inventory';
import { random } from '../../util';
import express from 'express';
import loaders from '../../../src/loaders';
import Inventory from '../../../src/models/inventory';
import startServer from '../../../src/server';
let body;
beforeAll(async done => {
// const expressApp = express();
// await loaders({ expressApp });
await startServer();
body = random.inventory();
done();
});
describe('Inventory Service', () => {
it('should add inventory', async () => {
const InventoryModel: typeof Inventory = Container.get('InventoryModel');
const Logger = Container.get('logger');
const inventoryService = new InventoryService(InventoryModel, Logger);
const { inventory } = await inventoryService.addInventory(body);
expect(inventory).toBeDefined();
expect(inventory.id).toBeDefined();
expect(inventory.title).toBe(body.title);
});
});
when I do like above, all test cases passes.
now I am trying to start up the server in global.setup.ts file, not in beforeAll of each test case file
so I am doing like that in jest.config.ts file
// A path to a module which exports an async function that is triggered once before all test suites
globalSetup : "./tests/jest/globalSetup.ts",
// A path to a module which exports an async function that is triggered once after all test suites
globalTeardown : "./tests/jest/globalTearDown.ts",
and I have added 2 files
globalSetup.ts
import startServer from '../../src/server'
const globalSetup = async () => {
await startServer();
}
export default globalSetup;
and in globalTearDown.ts
const globalTearDown = async () => {
// will close down that server instance
}
export default globalTearDown
when I do test now it is throwing an error like below
_ServiceNotFoundError: Service "InventoryModel" was not found, looks like it was not registered in the container. Register it by calling Container.set("InventoryModel", ...) before using service._
When running npm run build, getting Property 'error' does not exist on type '{}' in src/api/middlewares/attachCurrentUser.ts on Logger. Why?
I'm trying to write unit tests for service layer bu failing to create mock dependencies, mock Models mock EventDispatcher. Can anyone write an example unit test or at least show me the way to write them? Just one simple test example would help me progress so much, I can take it from there and write other tests by taking it as a reference. Thanks!
please help me out with deployment of the code in Azure Web App.
fixed 0 of 21 vulnerabilities in 863967 scanned packages
13 vulnerabilities required manual review and could not be updated
2 package updates for 8 vulnerabilities involved breaking changes
(use `npm audit fix --force` to install breaking changes; or refer to `npm audit` for steps to fix these manually)
When testing the routes from the user.ts and the auth.ts files in routes, the express.ts error method is giving me an error msg. It seems that the routes auth.ts and user.ts is not responding to express.ts. How can this be fixed or what am I doing wrong?
I read your article and checked your pattern and I was surprised that my architecture is very similar to yours. But I not found one thing I am searching for quite for a while:
How to handle more errors from Service? For example I have Service User
with method activateUser
. This method recieve activation token
and based on token's validity (user not already active, token exists, token not expired, user exists) I activate user or not.
The problem is that there is lot of things what can go wrong and throwing just one type of error is not good for clients. I am using i18n so I am translating keys to texts inside my controller.
Should I just throw those keys from my Service?
Now I am just trying to have every service method to do one thing so I can throw 1 error. But then I am moving too much stuff into controller:
not exists
error already active
error token expired
error error while saving
errorThis is just example and I have much more of this trough project, where I have controller full of one-time-use services because I need to return different error messages for each one of them.
Hope you understand my frustration and have some more expirience what you can share :)
Hey,
Thank you for this repo. 2 questions for you:
Where do you use the events?
Where do you put the line between a job and an event?
Hi!
Why are imports constantly used with "*" ?
import * as express from 'express';
Maybe I will add to the config ?
"esModuleInterop": true
And fix import on project?
Hey @santiq
First of all thank you so much for your articles and boilerplate code, you're a true inspiration and a great teacher.
Sorry to bother you if you're busy right now, I'm having some trouble with the dependency injection while testing the authentication process.
I get an error telling me the logger/userModel/eventDispatcher instances injected in the AuthenticationService constructor are undefined, the call stack traces back my users.ts route.
That just seems to happen If I instantiate the AuthenticationService in my test before calling the route, instantiation can either be by TypeDi container or by calling the AuthenticationService directly.
Here's the route I'm testing:
users.ts
import { Router, Request, Response, NextFunction } from 'express';
import { celebrate, Segments } from 'celebrate';
import User, { userJoiSchema } from '../../models/user';
import { IUserDTO } from '../../interfaces';
import { Container } from 'typedi';
import AuthenticationService from './../../services/authentication';
const route = Router();
export default (app: Router) => {
app.use('/users', route);
route.post(
'/',
celebrate({
[Segments.BODY]: userJoiSchema.registration,
}),
async (req: Request, res: Response, next: NextFunction) => {
const logger = Container.get<Utils.Logger>('logger');
try {
const userDTO: IUserDTO = req.body;
const userInDB = await User.findOne({
email: userDTO.email,
});
if (userInDB) return next({ status: 200, message: 'User already exists' });
const authenticationService = Container.get(AuthenticationService);
const { user, token } = await authenticationService.Register(userDTO);
res.status(201).json({
user,
token,
});
} catch (error) {
logger.error('๐ฅ error: %o', error);
return next(error);
}
},
);
};
As you can see nothing is dramatically different from your own auth route.
The authenticationService is also the same, I just changed some method names for personal clarity.
Here's the actual test trimmed down to isolate what's wrong
auth.test.ts
import request from 'supertest';
import http from 'http';
import User from '../../models/user';
import factory from '../../Utils/factory';
describe('Auth Test', () => {
let server: http.Server;
beforeAll(async (done) => {
server = await require('../../app').default; //Exporting the server promise from app.ts
done();
});
afterAll(async (done) => {
server.close();
await User.deleteMany({});
done();
});
it('should return 201 if request is valid', async (done) => {
const userDTO = factory.userDTO.build();
const res = await request(server).post('/users').send(userDTO);
expect(res.status).toBe(201);
done();
});
});
So this test passes without any problems:
And here's the same test but with a simple instantiation of the AuthenticationService:
auth.test.ts
import request from 'supertest';
import http from 'http';
import User from '../../models/user';
import factory from '../../Utils/factory';
import Container from 'typedi';
import AuthenticationService from '../../services/authentication';
import LoggerInstance from '../../loaders/logger';
import { mock } from 'jest-mock-extended';
import { EventDispatcherInterface } from '../../interfaces';
describe('Auth Test', () => {
let server: http.Server;
beforeAll(async (done) => {
server = await require('../../app').default;
done();
});
afterAll(async (done) => {
server.close();
await User.deleteMany({});
done();
});
it('should return 201 if request is valid', async (done) => {
const authenticationService = new AuthenticationService(
User,
LoggerInstance,
mock<EventDispatcherInterface>(),
);
// You can also instantiate it by container, it returns the same error
// const authenticationService = Container.get(AuthenticationService);
const userDTO = factory.userDTO.build();
const res = await request(server).post('/users').send(userDTO);
expect(res.status).toBe(201);
done();
});
});
Here's the error:
So I'm hoping to get some clarity in this because some of my other tests depend on the authService to register a user before using supertest to call the route.
Thank you for your time.
If i go through this structuring pattern , it seems to me that breaking this into say 2 micro-service would be very difficult. How would you go about this problem ?
(node:40779) DeprecationWarning: collection.ensureIndex is deprecated. Use createIndexes instead.
I've tried cutting the startServer()
method with an expressApp()
one that I can export:
export async function expressApp() {
const app = express();
await require('./loaders').default({ expressApp: app });
return app;
}
async function startServer() {
const app = await expressApp();
...
}
And in my test I try using it like I would normally do:
import request from 'supertest';
import { expressApp } from '../src/app';
describe('Analyze Movements', () => {
test('succeeds list of analyze movements', async () => {
const response = await request(expressApp)
.get(`/analyze`)
.expect(200);
let body = response.body;
expect(body.length).toBeGreaterThan(1);
});
}
But I'm getting a SyntaxError: SyntaxError: Unexpected identifier queries.js:1 ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,global,jest){import moment from 'moment';
I suspect this is a problem related with the fact that my expressApp() method is async but I can't figure it out..
Error: write EPIPE
at WriteWrap.afterWrite (net.js:868:14)
[nodemon] app crashed - waiting for file changes before starting...
Through some investigation, found how to implement model types to services, would like to implement it.
Hi Santiq,
Good Day!
Great work! I've been learning on this pattern and applying on a test project. But now I'm struggling about which layer should the form/input validation located at. I saw your example that you place the validation in the express middleware, but isn't these validation considered as business logic? Which mean shouldn't we place the validation at the service layer? Hope you can help me to understand on this part.
Thanks.
Hi, can you clear some things about agenda jobs? You're awaiting not async function
bulletproof-nodejs/src/services/mailer.ts
Line 10 in 9c1cd2a
Hello santiq,
I am back again:-)!. Hey i am having issues in unit testing container.get(service), checkout the below code.
import * as _ from 'lodash';
import {Service, Inject, Container} from 'typedi';
import OTPService from '../otp/index';
@Service()
export default class DevicechangeService {
constructor(@Inject('devicechangeModel')private devicechangeModel, @Inject('otpModel')private otpModel, @Inject('logger')private logger,) {}
/**
*
* Service to change the device
*
* @method deviceChange
* @param {number} accountId
* @param {string} deviceId
* @param {number} mobile
* @return {object} resolves after sending otp
*/
public async deviceChange(accountId : number, deviceId : string, newdeviceId : string, created : number, mobile : number,) : Promise < any > {
try {
await this
.devicechangeModel
.addDeviceChangeDetails(accountId);
let OTPServiceInstance = Container.get(OTPService);
await this
.otpModel
.updateOTPtoInactiveForModule('DEVICE_CHANGE', accountId);
//get the new otp
//get the user details
//send the otp
let result = await OTPServiceInstance.sendOTP(serviceprovider, mobile, otpMessage, isd);
if (result instanceof Object) {
return result;
}
return true;
} else {
return {
field: 421,
message: errorCode('account').getMessage(421)
};
}
} catch (e) {
throw e;
}
}
}
Here i am not able to set mock class for OTPService, so not able to alter container.get(OTPService) from the above code.
I have tried below but still it does'nt come inside mocked service.
@Service()
class OTPService {
public generateOTP : any = jest
.fn()
.mockImplementation(async(accountId : number, deviceId : string, status : string) => new Promise((resolve : any) => {
console.log('calls here')
resolve({})
}),);
public sendOTP : any = jest
.fn()
.mockImplementation(async(serviceprovider : string, mobile : number, otpMessage : string, isd : number) => new Promise((resolve : any) => resolve({})),);
}
describe('Account', () => {
beforeEach(done => {
Container.set(OTPService, new OTPService());
done();
});
describe('#deviceChange', () => {
it('check everyting cool', async done => {
let logger = {};
let otpModel = {
updateOTPtoInactiveForModule: jest
.fn()
.mockImplementation(async(deviceChange : string, accountId : number) => new Promise((resolve : any) => resolve(true))),
getUserPhoneService: jest
.fn()
.mockResolvedValue(true)
};
let devicechangeModel = {
addDeviceChangeDetails: jest
.fn()
.mockResolvedValue(true),
updateDeviceChangeToInactive: jest
.fn()
.mockResolvedValue(true)
};
// Container.set('OTPService', new OTPService());
let DeviceChangeServiceInstance = new DeviceChangeService(devicechangeModel, otpModel, logger);
let deviceChangeStatus = DeviceChangeServiceInstance.deviceChange(accountId, deviceId, newdeviceId, created, mobile);
});
});
});
Guys any idea how to solve this...
Hi @santiq,
How can I add pagination to mongoose models?
Greetings,
Would you mind explain/comment the following construct, which I'm not familiar with :
public async SignUp(userInputDTO: IUserInputDTO): Promise<{ user: IUser; token: string }>
export default ({ mongoConnection, models }: { mongoConnection; models: { name: string; model: any }[] })
export default mongoose.model<IUser & mongoose.Document>('User', User);
Do they require a special package ? Where can I find a documentation ?
Thank you
In your injector you proclaim:
`/**
And yet there are no tests.
Hey - thanks for this.
I have a question: how would you go about splitting up a "service" to prevent it getting too large considering a service could potentially contain all business logic?
Hello Santiago,
Hey i am using your nice code structure to build my new application, i am new to typescript and typedi. I am stuck at injecting database connections at model
I am using postgresql database
Below file path i am adding postgres db connection
bulletproof-nodejs/src/loaders/dependencyInjector.ts
Container.set('db', db);
and in models folder
import { Service, Inject, Container } from 'typedi';
import { Pool } from 'pg';
class Status {
@Inject('db')
private db1;
constructor() {
}
public async getApiStatus(): Promise<string> {
console.log(this.db1); // **not working**
let pool: Pool = Container.get('db'); // **working fine**
//console.log(this.pool);
return new Promise<string>((resolve, reject) => {
pool.query('select * from status;', (error, results) => {
if (error) {
throw error;
}
console.log(results.rows);
});
resolve('success');
});
}
}
export default new Status();
I wanted to somehow inject the database into the model using @Inject.. and without adding container.get('db') at each methods and access the database using this.db1 Could you please give me some hints to achieve this.
Hi,
First of all thank you for sharing this node-js project skeleton, and writing a blog post about it :)
I was wondering if you are aware that event dispatch has been archived:
https://github.com/pleerock/event-dispatch#readme
Do you know by any chance if there is a maintained alternative?
Best!
Hi, thanks for your repo and article! I'd really like to understand how and where graphql schemas and resolvers can be embedded into this scaffold structure.
Thanks!
I installed the app and run npm run start
then got this error:
error: ๐ฅ Error on dependency injector loader: { TypeError: mailgun is not a function
at Object.exports.default (/var/www/html/bulletproof-nodejs/src/loaders/dependencyInjector.ts:17:34)
at Object.exports.default (/var/www/html/bulletproof-nodejs/src/loaders/index.ts:28:52)
at process._tickCallback (internal/process/next_tick.js:68:7)
[stack]:
'TypeError: mailgun is not a function\n at Object.exports.default (/var/www/html/bulletproof-nodejs/src/loaders/dependencyInjector.ts:17:34)\n at Object.exports.default (/var/www/html/bulletproof-nodejs/src/loaders/index.ts:28:52)\n at process._tickCallback (internal/process/next_tick.js:68:7)',
[message]: 'mailgun is not a function' }
(node:21788) UnhandledPromiseRejectionWarning: TypeError: mailgun is not a function
at Object.exports.default (/var/www/html/bulletproof-nodejs/src/loaders/dependencyInjector.ts:17:34)
at Object.exports.default (/var/www/html/bulletproof-nodejs/src/loaders/index.ts:28:52)
at process._tickCallback (internal/process/next_tick.js:68:7)
(node:21788) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:21788) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
but after I made this change in dependencyInjector.js file, the error fixed:
import * as mailgun from 'mailgun-js';
to import mailgun from 'mailgun-js';
If you agree with this solution I can submit a PR.
but I why this error just show for me?!
I'm trying to register a new user using Postman.
url: http://127.0.0.1:3000/api/auth/signup
POST
I select Body
and x-www-form-urlencoded
and as key:value I put:
name John
email [email protected]
password password
Content-Type in Headers is: application/x-www-form-urlencoded
The response is
{
"errors": {
"message": "celebrate request validation failed"
}
}
Why it doesn't work?
event-dispatch
is no longer supported, so do u recommend any new good replacement for it
Before the question, Thanks for the boilerplate it has been a breeze to use.
Is there any plan to implement agenda with typescript since it has a DefinetlyTyped type definitions available?
Hello, in your routes you have such file as auth.ts
And we're using celebrate library to check if all data from front-end in the right format.
route.post(
'/signup',
celebrate({
body: Joi.object({
name: Joi.string().required(),
email: Joi.string().required(),
password: Joi.string().required(),
}),
}),
If my name is number or another different type (not string) I have a response from the server like this.
{
"errors": {}
}
I would like to see response something like this
{
"errors": {"Wrong json data type"}
}
How can I do this ?
Thank you!
Hello santiq and team,
I am trying to install the bulletproofNJS. I am stuck in powershell with trying to install argon2 via npm. I am getting the error msg after the failed install:
C:\Program Files (x86)\MSBuild\Microsoft.Cpp\v4.0\V140\Platforms\x64\PlatformToolsets\v140\Toolset.targets(36,5): error MSB8036: The Windows SDK version 8.1 was not found. Install the required version of Windows SDK or change the SDK version in the project property pages or by right-clicking the solution and selecting "Retarget solution". [C:\Users[user]\Documents\bulletproof\bulletproof-nodejs-master\node_modules\argon2\build\libargon2.vcxproj]
I have already installed python27 and also configured the env variables. I need some help with the Windows SDK.
Kind Regards and thx
Node v14 is going into LTS in just over a month (see https://nodejs.org/en/about/releases/). Would it be smart to update this repo to work with Node v14?
Maybe it's not very important, but I understand that even if SendWelcomeEmail()
is declared as an async function
bulletproof-nodejs/src/services/mailer.ts
Line 10 in ee0e80d
in reality it's not:
bulletproof-nodejs/src/services/mailer.ts
Lines 22 to 23 in ee0e80d
In order to be async, and effectively return only when the email has been set (or in case of error), we would require something like:
return new Promise((resolve, reject) => this.emailClient.messages().send(data, function (error, body) {
if (error) {
reject ({ delivered: 0, status: 'error', error });
} else {
resolve ({ delivered: 1, status: 'ok' });
}
});
Hello, im learning nodejs thanks to your tutorial and i was wondering if it is a good practice to pass data through req object as you did here ?
Wouldn't be better to give it as parameter in the next middleware ?
Sorry if its off topic ๐ (and thanks for this repo)
Sorry, I am new to the NodeJS and don't have the proper idea to build the NodeJS API.
My question is that how can we build this "bulletproof-nodejs"?
I went through the "Package.json" file and found command to build this "npm run build".
But the above command is taking too much time in hours and not giving the typescripts transpiled files in "outDir": "./build".
The reason to build the project is to "Want to add new routes and services and those newly added routes and service methods should be available in output directory javascript/transpiled files".
Currently, this is not working and not getting newly added routes and service methods in the output/build folder.
Please help, thanks in advance.....
nodemon is not saved to devDependencies in package.json and the project will not start after a clone, npm install
and npm start
.
I am trying to use Jest and Supertest to test my endpoint.
I added server variable and module.export = server in app.ts, so supertest can retrieve the server to perform testing. However, I found the server is return before the dependency injection is ready. which cause an error TypeError: Cannot read property 'address' of null.
Could you please provide a sample of how to adopt jest and supertest with your design?
Hello! I was wondering what would be the correct way to implement socket.io in this project, any ideas?
Hi~. You said that "Put a .env file, that must never be committed" in your article.
Then how do you handle other environments like production, test, etc??
If each branch has different .env
and not committed, then how do other members know .env
info? and how do you deploy them?
Hello! I've been following this pattern for my personal project, but I am reaching a point where I think that the controller layer (the express routes) is getting a bit too big, below is an example from one of your articles.
export default (app) => {
app.get('/user/search-location', (req, res, next) => {
try {
const { lat, lng } = req.query;
Logger.silly('Invoking user service to search by location')
const users = UserService.SearchUserByLocation(lat, lng);
return res.json(users).status(200);
} catch(e) {
Logger.warn('We fail!')
return next(e);
}
})
}
Say, I need this endpoint to do A LOT and it's fairly complicated. It needs too query different tables, databases and each service that this calls relies on a previous service. Not to mention all the logging I need to do.
Would make it sense to separate the callback method to maybe a controller directory, and if so how would that look like?
Should I have another service that handles all the complicated parts.
Or should I separate it all into middlewares?
Basically, if my controller method callback is looking big, what steps should I take to reduce or separate it?
Hey! I'm kinda new to nodeJs (in fact, to almost every Javascript framework). I was looking for a clean architecture and I'm impressed by this one.
This might not be a proper question but wth.
I'm trying to implement a this architecture but with MySQL and Redis as cache. Should I create a loader for each one? I'm not using agenda, so what would be the proper way inject the connections?
(I'm sorry for my english, it's way too late here :P)
Thanks man
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.