Im using NodeJS/Angular.
I wanted to render a document using Carbone in NodeJS, and send the result in a http request.
Myfile.js:
router.get('/executeFusion/', async (req, res) => {
try {
// Data to inject
const data = {
firstname: 'BLB',
lastname: 'MAR'
};
carbone.render('./node_modules/carbone/examples/simple.odt', data, async function(err, result) {
if (err) {
return console.log(err);
}
});
const file = './result.odt';
res.download(file);
}
catch (err) {
errorDbHandler.sendErrorHttp(err, res);
}
});
MyComponent.ts:
this.fusionService.executeFusion()
.subscribe(data => {console.log(data)},
(error) => console.log(error));
MyService.ts :
export class FusionService {
constructor(private httpClient: HttpClient, private configService: ConfigService) { }
executeFusion() {
return this.httpClient.get<any>(`${this.configService.getUrlApi()}/api/Fusion/executeFusion/`);
}
}
But the, I got this error:
The main idea here is, generating a document in NodeJS, send it to Angular in order to download it.
The error is related to promise which is mentioned in the question:
this.fusionService.executeFusion().toPromise().then(res => {
console.log(res);
}).catch(e) {
console.log(e);
}
But when you request the Http request in angular, you should use httpClient to make it and it returns the observable like this:
this.fusionService.executeFusion()
.subscribe(data => {console.log(data)},
(error) =>. console.log(error));
And, to make the express server send the file as a downloaded one:
res.download(file);
xport class FusionService {
constructor(private httpClient: HttpClient, private configService: ConfigService) { }
executeFusion() {
return this.httpClient.get<any>(`${this.configService.getUrlApi()}/api/Fusion/executeFusion/`, {response: 'application/txt'}); // type should match the type you are sending from API
}
}
for more details check here.
Related
How should I import loginMember in Controller? I am developing a REST API and now I need to use code in a different file location. I am having an error in the controller. When I am calling loginMember. (Cannot find name 'loginMember'.ts(2304))
SERVICE
import MembersModel from '../models/MembersModel';
import BaseService from './BaseService';
import { createPasswordToHash } from '../scripts/utils/auth';
class MembersService extends BaseService {
constructor() {
super(MembersModel);
}
// loginMember
loginMember = async (email: any, password: any) => {
return new Promise(async (resolve, reject) => {
try {
let data = await this.BaseModel.findOne({
email: email,
password: createPasswordToHash(password),
});
return resolve(data);
} catch (error) {
return reject(error);
}
});
};
}
export default MembersService;
CONTROLLER
import BaseController from './BaseController';
import MembersService from '../services/MembersService';
import ApiError from '../errors/ApiError';
import { NextFunction, Request, Response } from 'express';
import { createPasswordToHash, generateAccessToken } from '../scripts/utils/auth';
import httpStatus from 'http-status';
class MembersController extends BaseController {
constructor(membersService: MembersService) {
super(membersService);
}
login = (req: Request, res: Response, next: NextFunction) => {
MembersService.loginMember(req.body)
.then((response: any) => {
if (response) {
const member = {
...response.toObject(),
accessToken: generateAccessToken(response.toObject()),
};
delete member.password;
delete member.createdAt;
delete member.updatedAt;
return res.status(httpStatus.OK).send(member);
}
return res.status(httpStatus.UNAUTHORIZED).send({ error: 'Invalid email or password' });
})
.catch((err: { message: string }) => {
return next(
new ApiError(err.message, httpStatus.UNAUTHORIZED, 'login', req.headers['user-agent']?.toString() || 'Unknown')
);
});
};
}
export default new MembersController(new MembersService());
Now I am gettig a new error: "Property 'loginMember' does not exist on type 'typeof MembersService'.ts(2339)"
You're trying to call loginMember as a static method, but it's not defined as one. You'll have to use an instance of MembersService to use the method. Since your MembersController is already being initialized with a MembersService instance, you may just want to have a membersService property on the MembersController. Also, the loginMember method takes an email and a password, so you'll have to pass those arguments explicitly instead of just passing the request body. (I'm not sure where the email and password are in the request body though, so I can't help you there.) So with those changes, it would look like:
class MembersController extends BaseController {
private membersService: MembersService;
constructor(membersService: MembersService) {
super(membersService);
this.membersService = membersService;
}
login = (req: Request, res: Response, next: NextFunction) => {
this.membersService.loginMember(email, password) // <- Get these from the request
.then((response: any) => {
if (response) {
const member = {
...response.toObject(),
accessToken: generateAccessToken(response.toObject()),
};
delete member.password;
delete member.createdAt;
delete member.updatedAt;
return res.status(httpStatus.OK).send(member);
}
return res.status(httpStatus.UNAUTHORIZED).send({ error: 'Invalid email or password' });
})
.catch((err: { message: string }) => {
return next(
new ApiError(err.message, httpStatus.UNAUTHORIZED, 'login', req.headers['user-agent']?.toString() || 'Unknown')
);
});
};
One other code style suggestion would be to use async await instead of .then in the login method. Also, the Promise wrapping in the loginMember method looks unnecessary, and using an async function as the argument is an antipattern. The following should get the job done while avoiding those pitfalls:
loginMember = (email: any, password: any): Promise<Response> => {
return this.BaseModel.findOne({
email: email,
password: createPasswordToHash(password),
});
};
I am using nestjs, graphql, & prisma. I am trying to figure out how to pass my jwt token for each database request to the prisma service iv created. Iv tried an object to the constructor but then wont compile saying I am missing a dependency injection for whatever I reference in the constructor paramter.
#Injectable()
export class PrismaService
extends PrismaClient
implements OnModuleDestroy {
constructor() {
super();
//TODO how do I pass my jwt token to this for each request?
this.$use(async (params, next) => {
if (params.action === 'create') {
params.args.data['createdBy'] = 'jwt username goes here';
}
if (params.action === 'update') {
params.args.data['updatedBy'] = 'jwt username goes here';
}
const result = await next(params);
return result;
});
}
async onModuleDestroy() {
await this.$disconnect();
}
}
Are you using a nest middleware?
JWT is normally passed to a Controller, not a service.
Example:
#Injectable()
export class MyMiddleware implements NestMiddleware {
private backend: any // This is your backend
constructor() {
this.backend = null // initialize your backend
}
use(req: Request, res: Response, next: any) {
const token = <string>req.headers.authorization
if (token != null && token != '') {
this.backend
.auth()
.verifyIdToken(<string>token.replace('Bearer ', ''))
.then(async (decodedToken) => {
const user = {
email: decodedToken.email,
uid: decodedToken.uid,
tenantId: decodedToken.tenantId,
}
req['user'] = user
next()
})
.catch((error) => {
log.info('Token validation failed', error)
this.accessDenied(req.url, res)
})
} else {
log.info('No valid token provided', token)
return this.accessDenied(req.url, res)
}
}
private accessDenied(url: string, res: Response) {
res.status(403).json({
statusCode: 403,
timestamp: new Date().toISOString(),
path: url,
message: 'Access Denied',
})
}
}
So every time I get an API call with a valid token, the token is added to the user[] in the request.
In my Controller Class I can then go ahead and use the data:
#Post()
postHello(#Req() request: Request): string {
return 'Hello ' + request['user']?.tenantId + '!'
}
I just learned about an update in Nest.js which allows you to easily inject the header also in a Service. Maybe that is exactly what you need.
So in your service.ts:
import { Global, INestApplication, Inject, Injectable, OnModuleInit, Scope } from '#nestjs/common'
import { PrismaClient } from '#prisma/client'
import { REQUEST } from '#nestjs/core'
#Global()
#Injectable({ scope: Scope.REQUEST })
export class PrismaService extends PrismaClient implements OnModuleInit {
constructor(#Inject(REQUEST) private readonly request: any) {
super()
console.log('request:', request?.user)
}
async onModuleInit() {
// Multi Tenancy Middleware
this.$use(async (params, next) => {
// Check incoming query type
console.log('params:', params)
console.log('request:', this.request)
return next(params)
})
await this.$connect()
}
async enableShutdownHooks(app: INestApplication) {
this.$on('beforeExit', async () => {
await app.close()
})
}
}
As you can see in the log output, you have access to the entire request object.
In my Node.js app I return an error like this:
app.get('/api/login', (req, res, next) => {
//...
return res.status(400).send({
isSuccess: false,
errors: ["error 1", "error 2"]
})
})
In Angular, how can I get the error?
login() {
const headers = new HttpHeaders().set('Accept', 'application/json').set('Content-Type', 'application/json');
this.http.post('http://localhost:3000/api/login', { username: 'arwels', password: '24899632' }, { headers: headers }).subscribe(response => {
// ok
}, (err) => {
console.log(err) // Bad Reqeust
});
}
When I print err in the error section, it prints Bad Reqeust. Where is the object that is sent by the server?
You can use an HttpInterceptor to capture error responses from your API.
Ref: https://angular.io/api/common/http/HttpInterceptor
Here's an Example:
export class MyHttpInterceptor implements HttpInterceptor {
constructor() {
}
intercept( req: HttpRequest<any>, next: HttpHandler ): Observable<HttpEvent<any>> {
return next.handle(request).pipe(
catchError(async (_err: HttpErrorResponse, _caught: any) => {
switch (_err.status) {
case 401:
...
break;
case 500:
...
break;
default:
...
break;
}
return of(_err);
})
) as any;
}
}
Since you have full control over how you are returning your errors in your API, you can tailor the HttpInterceptor to work with any error object you want to create on your backend.
Unfavorable Option
If you just want the entire response so you can sniff out the statusCode, you can also just {observe: 'response'} in the HttpHeaders.
this.http.get<HttpResponse<any>>(<url>, {observe: 'response'}).pipe(
tap(resp => console.log('response', resp))
);
I'm looking to see form-data in my NestJS Guards. I've followed the tutorial, however, I'm not seeing the request body for my form-data input. I do see the body once I access a route within my controller, however.
Here's some code snippets of what I'm working with:
module.ts
...
#Module({
imports: [
MulterModule.register({
limits: { fileSize: MULTER_UPLOAD_FILESIZE_BYTES },
}),
],
controllers: [MainController],
providers: [
MainService,
AuthGuard,
],
})
...
AuthGuard.ts
import { Injectable, CanActivate, ExecutionContext } from '#nestjs/common';
import { Observable } from 'rxjs';
#Injectable()
export class AuthGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest(); // body is empty if form-data is used
return true;
}
}
MainController.ts
...
#Post("/upload")
#UseInterceptors(AnyFilesInterceptor())
#UseGuards(AuthGuard)
async upload(
#Body() body: UploadDTO,
#UploadedFiles() files: any[]
): Promise<any> {
console.log(body) // works as expected, whether form-data is used or not
...
}
...
Any feedback would be greatly appreciated!
NestJS guards are always executed before any middleware. You can use multer manually on the request object you get from the context.
import * as multer from 'multer'
...
async canActivate(context: ExecutionContext): Promise<boolean> {
const request: Request = context.switchToHttp().getRequest();
const postMulterRequest = await new Promise((resolve, reject) => {
multer().any()(request, {}, function(err) {
if (err) reject(err);
resolve(request);
});
});
// postMulterRequest has a completed body
return true;
}
If you want to use the #UploadedFiles decorator, you need to clone the request object before modifying it in your guard.
Of course you need to have installed the multer module with:
npm install multer
Posting my solution in-case it helps other devs dealing with the same issue.
To start, I created a middleware to handle the conversion of the multipart form data request. You could also inline this in to your guard if you only have one or two. Much of this code is plagiarised from the source code, and is not fully tested:
const multerExceptions = {
LIMIT_PART_COUNT: 'Too many parts',
LIMIT_FILE_SIZE: 'File too large',
LIMIT_FILE_COUNT: 'Too many files',
LIMIT_FIELD_KEY: 'Field name too long',
LIMIT_FIELD_VALUE: 'Field value too long',
LIMIT_FIELD_COUNT: 'Too many fields',
LIMIT_UNEXPECTED_FILE: 'Unexpected field',
}
function transformException(error: Error | undefined) {
if (!error || error instanceof HttpException) {
return error
}
switch (error.message) {
case multerExceptions.LIMIT_FILE_SIZE:
return new PayloadTooLargeException(error.message)
case multerExceptions.LIMIT_FILE_COUNT:
case multerExceptions.LIMIT_FIELD_KEY:
case multerExceptions.LIMIT_FIELD_VALUE:
case multerExceptions.LIMIT_FIELD_COUNT:
case multerExceptions.LIMIT_UNEXPECTED_FILE:
case multerExceptions.LIMIT_PART_COUNT:
return new BadRequestException(error.message)
}
return error
}
#Injectable()
export class MultipartMiddleware implements NestMiddleware {
async use(req: Request, res: Response, next: NextFunction) {
// Read multipart form data request
// Multer modifies the request object
await new Promise<void>((resolve, reject) => {
multer().any()(req, res, (err: any) => {
if (err) {
const error = transformException(err)
return reject(error)
}
resolve()
})
})
next()
}
}
Then, I applied the middleware conditionally to any routes which accept multipart form data:
#Module({
controllers: [ExampleController],
imports: [...],
providers: [ExampleService],
})
export class ExampleModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(MultipartMiddleware).forRoutes({
path: 'example/upload',
method: RequestMethod.POST,
})
}
}
Finally, to get the uploaded files, you can reference req.files:
#Controller('example')
export class ExampleController {
#Post('upload')
upload(#Req() req: Request) {
const files = req.files;
}
}
I expanded this in my own codebase with some additional supporting decorators:
export const UploadedAttachment = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest()
return request.files?.[0]
}
)
export const UploadedAttachments = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest()
return request.files
}
)
Which ends up looking like:
#Controller('example')
export class ExampleController {
#Post('upload')
upload(#UploadedAttachments() files: Express.Multer.File[]) {
...
}
}
I have my object returned from the server response, which i can see in the networks tab response on google chrome dev tools.
module.exports = (req, res) => {
var obj = {
name: "Thabo",
age: 23
};
res.json({obj})
};
And I have the angular2-typescript service that uses a promise to get the response observable from the server
export class MyService {
constructor(private http: Http) { }
getMessage(): Promise<any> {
return this.http.get('/map-moving-agents')
.toPromise()
.then((res)=> {
console.log(res.json().data);
res.json().data;
})
.catch(this.handleError);
}
private handleError(error: any): Promise<any> {
console.error('An error occurred', error);
return Promise.reject(error.message || error);
}
}
I even logged the response on the 'then' of the promise and it logs undefined.
And this is the component that uses the service
export class MapMovingAgents implements OnInit{
msg: {};
constructor(private myService: MyService){}
getMessage(): void {
this.myService.getMessage().then((res) => { this.msg = res;})
console.log(this.msg);
}
ngOnInit(): void {
this.getMessage();
}
}
getMessage(): void {
this.myService.getMessage().then((res) => { this.msg = res;})
console.log(this.msg);
}
will log undefined because getMessage() function is an async function.
Change it to
getMessage(): void {
this.myService.getMessage().then((res) => {
this.msg = res;
console.log(this.msg);
});
}
And your response doesn't have a data field so res.json().data; wil be undefined return only res.json() or res.json().obj
You need to return the final value for the rest of your code to use
return this.http.get('/map-moving-agents')
.toPromise()
.then((res)=> {
console.log(res.json().data);
return res.json().data; <--------------
})
.catch(this.handleError);
}