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();
Related
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.
I'm using Nest version ^6.7.2
I'm trying to create a createParamDecorator that gets the req.user value from a request.
Inside the createParamDecorator, the req.user has a value, however when I try to get the value in a controller by using the decorator the value is undefined.
const AuthSession = createParamDecorator((data, req) => {
console.log(req.user); // session data
return req.user;
});
Controller()
export default class AuthController {
#Get("/token/ping")
#UseGuards(AuthGuard("jwt"))
tokenPing(#AuthSession() session: Session) {
console.log(session); // undefined
return session;
}
}
Edit: I just tried updating to nestjs v7 and I'm having the same issue
import { createParamDecorator, ExecutionContext } from "#nestjs/common";
const AuthSession = createParamDecorator((data: any, ctx: ExecutionContext) => {
return { message: "asdf" };
});
export default AuthSession;
#Controller()
export default class AuthController {
#Get("/token/ping")
#UseGuards(AuthGuard("jwt"))
tokenPing(#AuthSession() session: Session) {
console.log(session); // undefined
return session;
}
}
you can get the information firs from ExecutionContext:
import { createParamDecorator, ExecutionContext } from '#nestjs/common';
export const User = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user;
},
);
check the example in the doc : Custom decorator
I figured out what the issue was. I had a custom validation PipeTransform that returned undefined if the ArgumentMetadata.type was neither "body" nor "param". So now I just return the first argument of the validator's transform method (the input) if the ArgumentMetadata.type is neither "body" nor "param" and that fixed the problem.
In the main.ts file of my nestJS application I would like to add some fixture data to my database if the app is starting in dev mode:
const app = await NestFactory.create(AppModule)
await app.listen(port, async () => {
if (!production) {
const User = this.db.collection('user')
await User.deleteMany({})
await User.insertMany(user)
}
})
Of course this is not working, as I do not have db at this time.
I'm defining the database connection in a module and this is how my database.module.ts looks like.
Is it possible to put the fixtures parts (drop database and add fixture data) in the database.module? The reason why I think I have to add it to the main.ts is that I need to run it on application start, not at every db connection.
import { Module, Inject } from '#nestjs/common'
import { MongoClient, Db } from 'mongodb'
#Module({
providers: [
{
provide: 'DATABASE_CLIENT',
useFactory: () => ({ client: null })
},
{
provide: 'DATABASE_CONNECTION',
inject: ['DATABASE_CLIENT'],
useFactory: async (dbClient): Promise<Db> => {
try {
const client = await MongoClient.connect('mongodb://localhost:27017')
dbClient.client = client
const db = client.db('database')
return db
} catch (error) {
console.error(error)
throw error
}
}
}
],
exports: ['DATABASE_CONNECTION', 'DATABASE_CLIENT']
})
export class DatabaseModule {
constructor(#Inject('DATABASE_CLIENT') private dbClient) {}
async onModuleDestroy() {
await this.dbClient.client.close()
}
}
With that I can use my db in every other module, but that doesn't help me to get db connection at start up of the application:
import { Module } from '#nestjs/common'
import { MyService } from './my.service'
import { MyResolvers } from './my.resolvers'
import { DatabaseModule } from '../database.module'
#Module({
imports: [DatabaseModule],
providers: [MyService, MyResolvers]
})
export class MyModule {}
If you're looking to get the database client in your main.ts you need to get into the client from the container system nest has. You can do that with the following line, used before your app.listen()
const db = app.get('DATABASE_CLIENT', { strict: false })
And then instead of this.db you'll just use db.client.collection()
I'm having some trouble hitting a POST endpoint that triggers a typeorm repository.save() method to my postgres DB.
Here's my DTO object:
import { ApiProperty } from '#nestjs/swagger/';
import { IsString, IsUUID} from 'class-validator';
import { Client } from '../../../models';
import { User } from '../../../user.decorator';
export class ClientDTO implements Readonly<ClientDTO> {
#ApiProperty({ required: true })
#IsUUID()
id: string;
#ApiProperty({ required: true })
#IsString()
name: string;
public static from(dto: Partial<ClientDTO>) {
const cl = new ClientDTO();
cl.id = dto.id;
cl.name = dto.name;
return cl;
}
public static fromEntity(entity: Client) {
return this.from({
id: entity.id,
name: entity.name,
});
}
public toEntity = (user: User | null) => {
const cl = new Client();
cl.id = this.id;
cl.name = this.name;
cl.createDateTime = new Date();
cl.createdBy = user ? user.id : null;
cl.lastChangedBy = user ? user.id : null;
return cl;
}
}
My controller at POST - /client:
import {
Body,
Controller,
Get, Post
} from '#nestjs/common';
import { ClientDTO } from './dto/client.dto';
import { ClientService } from './client.service';
import { User } from 'src/user.decorator';
#Controller('client')
export class ClientController {
constructor(
private clientService: ClientService
) { }
#Get()
public async getAllClients(): Promise<ClientDTO[]> {
return this.clientService.getAllClients();
}
#Post()
public async createClient(#User() user: User, #Body() dto: ClientDTO): Promise<ClientDTO> {
return this.clientService.createClient(dto, user);
}
}
And my service:
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { Repository } from 'typeorm';
import { Client } from '../../models';
import { ClientDTO } from './dto/client.dto';
import { User } from '../../user.decorator';
#Injectable()
export class ClientService {
constructor(
#InjectRepository(Client) private readonly clientRepository: Repository<Client>
) {}
public async getAllClients(): Promise<ClientDTO[]> {
return await this.clientRepository.find()
.then(clients => clients.map(e => ClientDTO.fromEntity(e)));
}
public async createClient(dto: ClientDTO, user: User): Promise<ClientDTO> {
return this.clientRepository.save(dto.toEntity(user))
.then(e => ClientDTO.fromEntity(e));
}
}
I get a 500 internal server error with log message stating that my ClientDTO.toEntity is not a function.
TypeError: dto.toEntity is not a function
at ClientService.createClient (C:\...\nest-backend\dist\features\client\client.service.js:29:47)
at ClientController.createClient (C:\...\nest-backend\dist\features\client\client.controller.js:27:35)
at C:\...\nest-backend\node_modules\#nestjs\core\router\router-execution-context.js:37:29
at process._tickCallback (internal/process/next_tick.js:68:7)
I'm confused because this only happens via http request. I have a script that seed my dev database after I launch it fresh in a docker container called seed.ts:
import * as _ from 'lodash';
import { Client } from '../models';
import { ClientDTO } from '../features/client/dto/client.dto';
import { ClientService } from '../features/client/client.service';
import { configService } from '../config/config.service';
import { createConnection, ConnectionOptions } from 'typeorm';
import { User } from '../user.decorator';
async function run() {
const seedUser: User = { id: 'seed-user' };
const seedId = Date.now()
.toString()
.split('')
.reverse()
.reduce((s, it, x) => (x > 3 ? s : (s += it)), '');
const opt = {
...configService.getTypeOrmConfig(),
debug: true
};
const connection = await createConnection(opt as ConnectionOptions);
const clientService = new ClientService(connection.getRepository(Client));
const work = _.range(1, 10).map(n => ClientDTO.from({
name: `seed${seedId}-${n}`,
}))
######################## my service calls ClientDTO.toEntity() without issue ###########################
.map(dto => clientService.createClient(dto, seedUser)
.then(r => (console.log('done ->', r.name), r)))
return await Promise.all(work);
}
run()
.then(_ => console.log('...wait for script to exit'))
.catch(error => console.error('seed error', error));
It makes me think I am missing something simple/obvious.
Thanks!
Looks like you are using ValidationPipe. The solution is mentioned here
https://github.com/nestjs/nest/issues/552
when setting your validation pipe you need to tell it to transform for example
app.useGlobalPipes(new ValidationPipe({
transform: true
}));
The fact that the dto is declared like this dto: ClientDTO in the controller is not enough to create instances of the class. This is just an indication for you and the other developers on the project, to prevent misuses of the dto object in the rest of the application.
In order to have instances of classes, and use the methods from the class, you have to explicitly set a mapping like this:
#Post()
public async createClient(#User() user: User, #Body() dto: ClientDTO): Promise<ClientDTO> {
const client = ClientDTO.from(dto);
return this.clientService.createClient(client, user);
}
Assuming ClientDTO.from is the right function to use for the data contained in dto. If not, adapt it, create a new one, or add a constructor.
Your dto was not a class-based object when coming in through your api call-- it's just a generic object. Therefore it can't have methods and so your toEntity method won't work. The error message you get is a red herring that doesn't tell you the true cause of the failure.
You can fix this by creating a new object based on your class and then calling a method on the new object to copy the properties in from your plain object dto, or by using the class-transformer library, or by whatever you want that achieves the same result.
I'm trying to extends the KUZZLE JavaScript SDK in order to call some controllers on kuzzle servers, implemented via plugins.
I'm following that guide: add controller
Here is my controller which extends from the BaseController:
const { BaseController } = require('kuzzle-sdk');
export class UserController extends BaseController {
constructor (kuzzle) {
super(kuzzle, 'plugins-user/userController');
}
/**
* Method to call the action "CreateAccount" on the UserController
* #param {*} user
*/
async createAccount(user) {
const apiRequest = {
action: 'new',
body: {
user
}
};
try {
const response = await this.query(apiRequest);
return response.result.user;
}
catch (error) {
//Manage errors
}
}
}
And here is where I specify the controller in order to use it further in the App, on the creation of the singleton.
const {UserController} = require('./UserController');
const { Kuzzle, WebSocket } = require('kuzzle-sdk');
class KuzzleService {
static instance = null;
static async createInstance() {
var object = new KuzzleService();
object.kuzzle = new Kuzzle(
new WebSocket('localhost'),{defaultIndex: 'index'}
);
object.kuzzle.useController(UserController, 'user');
await object.kuzzle.connect();
const credentials = { username: 'admin', password: 'pass' };
const jwt = await object.kuzzle.auth.login('local', credentials);
return object;
}
static async getInstance () {
if (!KuzzleService.instance) {
KuzzleService.instance = await KuzzleService.createInstance();
}
return KuzzleService.instance;
}
}
export default KuzzleService;
Somehow I'm getting the following error:
Controllers must inherit from the base controller
Is there something wrong with the imports ?
I've found out the solution to that issue. Firstly, I was not on the right version of the kuzzle SDK released recently (6.1.1) and secondly the controller class must be exported as default:
const { BaseController } = require('kuzzle-sdk');
export default class UserController extends BaseController {
constructor (kuzzle) {
super(kuzzle, 'plugins-user/userController');
}
/**
* Method to call the action "CreateAccount" on the UserController
* #param {*} user
*/
async createAccount(user) {
const apiRequest = {
action: 'new',
body: {
user
}
};
try {
const response = await this.query(apiRequest);
return response.result.user;
}
catch (error) {
//Manage errors
}
}
}
And then the UserController needs to be importer that way:
import UserController from './UserController.js'
Then, as specified in the documentation, we need just inject the kuzzle object into the controller that way:
kuzzle.useController(UserController, 'user');