I have start backend node + express + typescript + tsyringe.
I started to face the problem of storing data in a class property
Example:
// myRouter.ts
import 'reflect-metadata';
import { container } from 'tsyringe'
import { MyController } from './MyController.ts'
export const router = (app: Application) => {
const myController = container.resolve(MyController)
app.get('/myEndpoint', myController.getAllData)
}
// myController.ts
#injectable()
export class LocationsController extends BaseController {
myString: string = 'empty'
getAllData(req, res, next) {
console.log(this.myString) // myString: 'empty'
this.myString = 'not empty!!'
console.log(this.myString) // myString: 'not empty!!'
return this.myString
}
}
If I call again, the class will already have a loaded instance and there will already be data that we recorded.
// myController.ts
#injectable()
export class LocationsController extends BaseController {
myString: string = 'empty'
getAllData(req, res, next) {
console.log(this.myString) // myString: 'not empty!!'
this.myString = 'not empty!!'
console.log(this.myString) // myString: 'not empty!!'
return this.myString
}
}
Actually it will be solved if I do something like this:
// myRouter.ts
import 'reflect-metadata';
import { container } from 'tsyringe'
import { MyController } from './MyController.ts'
export const router = (app: Application) => {
app.get('/myEndpoint', (req, res, next) => {
const myController = container.resolve(MyController)
return myController.getAllData(req, res, next)
})
}
I would like to ask you if it is good practice to use class properties in node + express?
And is there a way to write the controller loading from the router more beautifully and correctly without such cumbersome constructions where you need to create the same controller instance every time. We can have many endpoints, for example 20, and all of them will use MyController.
Thanks in advance for your answers, feel free to ask questions or ask for more information, I can provide everything.
I've tried instantiating the class each time the client requests an endpoint, but it seems cumbersome to me.
Related
I have two file alarm.js and notifications.js. In alarm.js I need to call a method called sendPush from notifications.js.
What I've tried :
Exporting the function from notifications.js:
module.exports.sendPush = function(params){
console.log("sendPush from notifcations.js called");
console.log(params);
}
Importing it in alarm.js and use it :
let helperNotif = require('./notifications')
router.post("/", async (req, res) => {
const params = {
param1: 'a',
param2: 'b'
}
helperNotif.sendPush(params)
});
The problem:
I keep getting the error saying helperNotif.sendPush is not a function
The question :
How can I call this notification.js sendPush function from my alarm.js file ?
[EDIT] maybe I should add that in notifications.js I have some router.get and router.post and at the end module.exports = router;
If your notifications.js ends with module.exports = router, that will overwrite your module.exports.sendPush = .... If you want to export both the router and the sendPush, you can write
function sendPush(params){
console.log("sendPush from notifcations.js called");
console.log(params);
}
...
module.exports = {router, sendPush};
To import the router elsewhere, you must then write
const {router} = require("./notifications.js");
Trying to convert a Node.js project that uses Express.js.
The end goal is something similar to what I've already got in the App.ts file. In vanilla Javascript, the solution is the same, but instead of a class, it's a module.exports variable function.
export class UserRouter {
constructor() {}
// Open to all users
register() {
router.post('/account-register', AuthController.prototype.register);
}
login() {
router.post('/account-login', AuthController.prototype.login);
}
logout() {
router.get('/account-logout', AuthController.prototype.logout);
}
forgotPassword() {
router.post(
'/account-password-forgot',
AuthController.prototype.forgotPassword
);
}
resetPassword() {
router.patch(
'/account-password-reset/:token',
AuthController.prototype.resetPassword
);
}
// Require Login for all subsequent routes
protectedRoutes() {
router.use(AuthController.prototype.protectedRoutes);
}
updateUserProfile() {
this.protectedRoutes;
router.patch(
'/account-update-profile',
UserController.prototype.uploadUserPhoto,
UserController.prototype.updateMyProfile
);
}
updateUserSettings() {
this.protectedRoutes;
router.patch(
'/account-update-settings',
UserController.prototype.updateUserSettings
);
}
deactivateUserAccount() {
this.protectedRoutes;
router.delete(
'/account-deactivate',
UserController.prototype.deactivateUser
);
}
// Only the delcared user roles can access the subsequent routes
limitedAccessByUserRole() {
router.use(
AuthController.prototype.restrictToRoles(
'employee-admin',
'employee-super-admin'
)
);
}
getAllUsers() {
this.protectedRoutes;
this.limitedAccessByUserRole;
router.route('/').get(UserController.prototype.getAllUsers);
}
createUser() {
this.protectedRoutes;
this.limitedAccessByUserRole;
router.route('/').post(UserController.prototype.createUser);
}
getUser() {
this.protectedRoutes;
this.limitedAccessByUserRole;
router.route('/:id').get(UserController.prototype.getUser);
}
updateUser() {
this.protectedRoutes;
this.limitedAccessByUserRole;
router.route('/:id').patch(UserController.prototype.updateUser);
}
deleteUser() {
this.protectedRoutes;
this.limitedAccessByUserRole;
router.route('/:id').delete(UserController.prototype.deleteUser);
}
}
The class was imported into the App.ts file with the variable UserRouter.
// Mounted API Routes
const apiVersion = '1.0';
const apiRoutes = `/api/v${apiVersion}`;
app.use(`${apiRoutes}/users`, UserRouter);
Looking for a solution. Any help is greatly appreciated. Full project code (minus ENV files) is on Github.
You can try to create a class property with all the routes, something like this.
user.network.ts
export default class UserRoutes {
public router: Router;
// remplace my controller for your controller
private controller: UserController = new UserController();
constructor() {
this.router = express.Router();
this.registerRoutes();
}
// remplace my example routes and controller methods for your own
protected registerRoutes(): void {
this.router.get('/:username',this.controller.getUserByUsername);
this.router.get('/', this.controller.paginationByUsername);
this.router.post('/', this.controller.createUser);
this.router.post('/login', this.controller.login);
this.router.put('/', this.controller.editUser);
}
}
routes.js
import express from 'express';
import UserRouter from './userRoutes.ts'
// if you want to add another router like news or something else
// you could add one 'server.use(...)' below the 'server.use('/user',...)
const routes = (server:express.Application): void => {
server.use('/user', new UserRoute().router);
};
export default routes
In server.js you could have something like this.
class Server {
public app: express.Application;
constructor() {
this.app = express();
this.config();
}
public config(): void {
this.app.set('port', 3000);
this.app.use(bodyParser.json());
this.app.use(bodyParser.urlencoded({ extended: false }));
routes(this.app)
}
public start(): void {
this.app.listen(this.app.get('port'), () => {
console.log('Server listening in port 3000');
});
}
}
const server = new Server();
server.start();
I'm trying to transpose an initial Node project to a TypeScript one using Express and CoreModel.
In my initial project, the architecture is as the following exemple:
to handle users
accountRouter <- accountController <- User (Class) <- CoreModel (parent Class of user)
User (Class): contain specific method according User need
CoreModel (parent Class): contain global method as CRUD
I have tried to keep this way of doing on TypeScript, with few modifications
accountRouter
import { Router } from 'express';
import { accountController } from '../controllers/accountController'
const router = Router()
const controller = new accountController()
router
.route('/account')
.get(controller.showUsers)
export default router
accountController
import { Request, Response } from 'express'
import { User } from '../models/User'
import * as bcrypt from 'bcrypt'
import { UserInformations, UserInformationsEdit } from '../models/interface'
export class accountController {
private user: User = new User();
public async showUsers(req: Request, res: Response): Promise<Response> {
try {
console.log('this', this);
const result = await this.user.findAll()
return res.json(result)
} catch (error) {
throw new Error('error: ' + error.message)
}
}
}
User
import { CoreModel } from './CoreModel'
export class User extends CoreModel {};
CoreModel
import db from "../config/db"
export abstract class CoreModel {
name: any;
public async findAll(): Promise<any[]> {
try {
const result = await db.query(
`SELECT * FROM "${this.name.toLowerCase()}"`
);
return result.rows;
} catch (error) {
throw new Error('error: ' + error.message)
}
}
}
This way I'm able to transpile my code, but when I try to reach http://localhost:1234/account
I got
Cannot read property 'user' of undefined
According to me, since I'm getting an instance of accountController in the router, this should be accountController, isn't it ?
On a broader level,, I'm pretty sure that the way of I'm building my API in TS is not that good, I have tried to find example on internet to reproduce concept, but I didn't find some cases according my need.
I will be glad to take from you some input to get over it
Paul
The problem is the following line:
router
.route('/account')
.get(controller.showUsers)
You pass controller.showUsers to the get-handler. By doing this, the this context will no longer refer to the controller. Fix it by using:
router
.route('/account')
.get(controller.showUsers.bind(controller))
As a side note: you may want to consider the NestJS framework to build a robust API that is based on Typescript.
So I have written code to handle this, essentially it looks for any placeholder.controller.ts file in my project and uses the exported array of controllers to add routing. Controllers have to be default exported in an array with a specific format.
the format looks like this:
const controller1 = {
endpoint: '/hello/world',
method: 'get',
controller: () => console.log('hello world!'),
}
export default [
controller1
];
The routing code that handles all of this, exists in a routes.ts file and looks like this:
import glob from 'glob';
import path from 'path';
import { Router } from 'express';
import { toArray } from '../lib/utilities/generic-utilities';
import { isRouteType, isArrayWithContent } from '../lib/utilities/type-checking';
import { skip } from '../lib/middleware/generic-middleware';
import { Route } from '../meta/#types/common-types';
import { secureRoutesConstant, extension } from './settings';
import secureRoute from '../lib/middleware/secure-route';
const router: any = Router({ mergeParams: true });
// relative path from routes file to controllers folder.
const controllersPath = '../http/controllers/';
const addRouteToRouter = (route: Route, filename: string) => {
const acceptableRoute: object | boolean = isRouteType(route);
const message: string = `issue with route while exporting a controller in file ${filename}\nroute supplied was:`;
if (!acceptableRoute) console.log(message, route);
if (!acceptableRoute) return;
const { endpoint, controller, method, isSecure = secureRoutesConstant } = route;
const { middlewareBefore = [], middlewareAfter = [] } = route;
const makeRouteSecure: Function = isSecure ? secureRoute : skip;
const middlewareBeforeArr: Function[] = toArray(middlewareBefore);
const middlewareAfterArr: Function[] = toArray(middlewareAfter);
const routeArguments: Function[] = [
...middlewareBeforeArr,
makeRouteSecure,
controller,
...middlewareAfterArr,
];
router.route(endpoint)[method](...routeArguments);
};
const addToRouterForEach = (allRoutes: Route[], filename: string) =>
allRoutes.forEach((route: Route) => addRouteToRouter(route, filename));
glob
.sync('**/*.ts', { cwd: path.join(`${__dirname}/`, controllersPath) })
.filter((filename: string) => filename.split('.').includes('controller'))
.map((filename: string) => ({ defaultsObj: require(`${controllersPath}${filename}`), filename }))
.filter(({ defaultsObj }) => isArrayWithContent(defaultsObj.default))
.forEach(({ defaultsObj, filename }) => addToRouterForEach(defaultsObj.default, filename));
export default router;
And is simply imported into app.ts and used like this:
app.use('/api', router)
Essentially this means I have no routing code as it's all handled for me, I only have to write my services, controllers and models.
Is there any performance or security issues with doing things like this, or with the code itself?
Is there any performance or security issues with doing things like this, or with the code itself?
Performance
No. The auto code will only run on boot and even if it takes a second its not a cost you are paying on individual client request route handling.
Security
The code architecture is secure by itself and does not increase your risk of vulnerabilites.
I'm building a nuxtjs app and try to set a cookie from a global middleware. I found this contribution on GitHub which shows a method to do this.
So I implemented my middleware like this
export default function ({ isServer, res, query }) {
if (query.lang) {
if (isServer) {
res.setHeader("Set Cookie", [`lang=${query.lang}`]);
} else {
document.cookie = `lang=${query.lang}`;
}
}
}
My problem is that when I visit my app with ?lang=xxx as a parameter, I'm always running into the else block of my if condition. So I get the error
document is not defined
Has anyone a idea what is wrong with my code. I can't see a difference to the code published on github.
You should use cookie-universal-nuxt.
Add this in your module section in nuxt.config.js:
['cookie-universal-nuxt', { alias: 'cookiz' }],
You can use it directly in the store with nuxtServerInit:
async nuxtServerInit({ commit, state, dispatch },
{ app, store, route, req, res, error, redirect }
) {
app.$cookiz.set('lang', route.query.lang)
})
Or in a middleware:
export default function ({ app, res, query }) {
if (query.lang) {
app.$cookiz.set('lang', query.lang)
}
}
You can using helpers from nuxt by using setCookie and custom middleware
https://nuxtjs.org/docs/configuration-glossary/configuration-servermiddleware/
middlewares/cookies.ts
export default function (req, res, next) {
let cookie = getCookie(req, '_id') || 'random_value'
setCookie(res, '_id', cookie)
// Don't forget to call next at the end if your middleware is not an endpoint
next()
}
Also update your nuxt.config.ts
export default defineNextConfig({
// ...
router: {
middleware: ["cookies"],
}
})
In Nuxt 3 and Nuxt 2 Bridge you can use useCookie
Nuxt provides an SSR-friendly composable to read and write cookies.
const lang = useCookie('lang')
lang.value = ''
export function getCookie(name, stringCookie) {
const matches = stringCookie.match(
new RegExp(
`(?:^|; )${name.replace(/([.$?*|{}()[\]\\/+^])/g, '\\$1')}=([^;]*)`,
),
);
return matches ? decodeURIComponent(matches[1]) : undefined;
}