I am learning about using typescript to build API's, I have come across two issues right now. First, I have a somewhat generic PostController Class that can accept a use-case that implements the PostMethod Interface, e.g
export interface PostMethod {
add: (req: Request, res: Response) => Promise<any> // not sure if it should be returning any
}
That's the interface, and the generic controller looks like this.
export class PostController implements PostMethod {
constructor(public postMethod: any) {}
async add(req: Request, res: Response) {
let { ...incomingHttpBody } = req.body
console.log('body', incomingHttpBody)
console.log(this.postMethod)
type Source = {
ip: string
browser: string | string[] | undefined
referrer: string | string[]
}
let source = {} as Source
source.ip = req.ip
source.browser = req.headers['User-Agent']
if (req.headers.Referer) {
source.referrer = req.headers.Referer
}
const newItem = await this.postMethod({ source, ...incomingHttpBody })
return apiResponse({
status: true,
statusCode: 201,
message: 'Resource created successfully',
data: [newItem]
})
}
}
And then, I can use this PostController class like this
...
const postMethod = new AddUser(UsersDb).addUser
export const postUser = new PostController(postMethod)
...
The AddUser class looks like this,
export class AddUser {
constructor(public usersDb: UserDatabase) {}
async addUser(userInfo: IUser) {
console.log({...userInfo})
const exists = await this.usersDb.findByEmail(userInfo.email)
if (exists) {
throw new UniqueConstraintError('Email address')
}
const user = new UserFactory(userInfo)
user.makeUser()
const { email, ...details } = user.user
const newUser = await this.usersDb.insert({ email, ...details })
const id = newUser.user._id
await createWallet(id)
// await publisher(id.toString(), 'newuser.verify')
// await consumer('verify_queue', verifyUser, '*.verify')
return newUser
}
}
When I do a console.log of req.body, I get the incoming body, but I keep getting TypeError: Cannot read property 'postMethod' of undefined. I am unsure how to annotate the constructor function also. I do not know what I could be doing wrong, when I console.log postUser, I do see the function passed as argument logged to the console, but when I try sending requests, it fails.
Please help, thank you.
I think there are some misconceptions here.
You're defining an interface PostMethod, which itself IS NOT a method. It is an interface. So if you try to pass an instance of this, please don't just pass a FUNCTION (or method). But pass an instance of PostMethod.
It could look like this (look at the changes I made):
export interface PostMethod {
add: (req: Request, res: Response) => Promise<any> // not sure if it should be returning any
}
export class PostController implements PostMethod {
constructor(public postMethod: any) {}
async add(req: Request, res: Response) {
// cloning? if so, do it like this
let incomingHttpBody = { ...req.body }
console.log('body', incomingHttpBody)
console.log(this.postMethod)
type Source = {
ip: string
browser: string | string[] | undefined
referrer: string | string[]
}
let source = {} as Source
source.ip = req.ip
source.browser = req.headers['User-Agent']
if (req.headers.Referer) {
source.referrer = req.headers.Referer
}
// change: I made a change here, you have to call postMethod.add!
const newItem = await this.postMethod.add({ source, ...incomingHttpBody })
return apiResponse({
status: true,
statusCode: 201,
message: 'Resource created successfully',
data: [newItem]
})
}
}
// change: instantiate AddUser which in turn implements PostMethod that can be passed...
const postMethod = new AddUser(UsersDb)
export const postUser = new PostController(postMethod)
// change: implementing PostMethod!
export class AddUser implements PostMethod {
constructor(public usersDb: UserDatabase) {}
// change: new method! this is a must, because we're implementing PostMethod!
async add(req: Request, res: Response) {
// this gets called, not addUser!
}
async addUser(userInfo: IUser) {
const exists = await this.usersDb.findByEmail(userInfo.email)
if (exists) {
throw new UniqueConstraintError('Email address')
}
const user = new UserFactory(userInfo)
user.makeUser()
const { email, ...details } = user.user
const newUser = await this.usersDb.insert({ email, ...details })
const id = newUser.user._id
await createWallet(id)
// await publisher(id.toString(), 'newuser.verify')
// await consumer('verify_queue', verifyUser, '*.verify')
return newUser
}
}
You need to instantiate the class first before you can use the method...
...
const postMethod = new AddUser(UsersDb)
//then use it
postMethod.addUser()
export const postUser = new PostController(postMethod)
...
However you could make it static so you do not need to instantiate it:
static async add(req: Request, res: Response) { ....
An function is usually written like this in an interface:
export interface PostMethod {
constructor(): void,
add(req: Request, res: Response): Promise<any>
}
I fixed the issue, after much digging and reading up I realized it was a problem with this losing its context. Hence why I was getting undefined when I do something like this.
const postMethod = new PostController(usecase)
The fix was to bind this in the constructor like so,
this.add = this.add.bind(this)
that fixed the issue as when the class was instantiated it doesn't just point to the methods of the class but also the associations of the class.
The postMethod parameter which is being passed into the constructor of PostMethod is not being stored anywhere. Store it ins a private class variable and then use the private class variable.
like -
const _postMethod = postMethod;
and then while calling call
const newItem = await this._postMethod({ source, ...incomingHttpBody })
Related
I am new to Unit Testing and wanted to stub dynamodb-onetable library. I was trying to stub getData() from getDetails.ts file but it shows that "OneTableArgError: Missing Name Property". Because this getProjectDetails() contain new Table() class.
How to stub dynamodb-onetable so that I can get data in dataDetails variable. I was doing something like this in getEmp.spec.ts
dataDetailsStub = sinon.stub(DataService , "getData");
------lambda.ts
import { DynamoDBClient } from '#aws-sdk/client-dynamodb';
import Dynamo from 'dynamodb-onetable/Dynamo';
export const client = new Dynamo({
client: new DynamoDBClient({
region: REGION, }),
});
-------DataService.ts
import { client } from '../lambda';
const workspaceTable = new Table({
client,
name: TableName,
schema,
logger: true,
partial: false,
});
const projectDetail = workspaceTable.getModel('empDetail');
export default class **DataService** {
static getData = async (empId: string, type: string) => {
const params = {
projectId,
type
};
const response = await empDetail.find(params);
logger.trace('response', { response });
return response; };
}
------getDetails.ts
const dataDetails= await DataService.getData(
empId,
'EMPLOYEE-SAVEDATA'
);
I was trying to stub the DataService.getData() but getting error saying "OneTableArgError: Missing "name" property". I want to get data in dataDetailsStub whatever i am sending while mocking the getData()
const dataDetailsStub = sinon.stub(DataService , "getData").return({emp object});
Can anyone help me out on this. I'm really got stuck in this. Thanks in advance
So, I searched for an existing solution, but I could find nothing, or maybe I'm not searching the correct way, thus, sorry if there's an existing thread about it.
In sum, it seems my code is not instantiating an object correctly as a class when it comes from an Axios call to the backend. So, when I call some function, I'm getting the error Uncaught TypeError TypeError: object.method is not a function.
Example:
First, basically, a parent component will call a service that will make a request to the backend. The result is then passed to a child component.
// imports
const Component: React.FC<ComponentProps> = () => {
const { id } = useParams<{ id: string }>();
const [object, setObject] = useState<Class>(new Class());
useEffect(() => {
(async () => {
try {
const object = await Service.getById(id);
setObject(object);
} catch (err) {
//error handling
} finally {
cleanup();
}
})();
return () => {
// cleanup
};
});
return (
<Container title={object.name}>
<Child object={object} />
</Container>
);
};
export default Component;
Then, in child component, let's say I try to call a method that was defined in the Class, there I'm getting the not a function error:
// imports
interface Interface {
object: Class;
}
const Child: React.FC<Interface> = ({ object }) => {
object.callSomeFunction(); // error starts here
return (
<SomeJSXCode />
);
};
export default Child;
Example of the Class code, I tried to write the method as a function, arrow function, and a getter, but none worked. Also, as a workaround, I've been defining a method to instantiate the object and set all properties, but I don't think that's a good long-term solution, and for classes with many properties, it gets huge:
export class Class {
id: string = '';
name: string = '';
callSomeFunction = () => {
// do something;
}
static from(object: Class): Class {
const newInstance = new Class();
newInstance.id = object.id;
newInstance.name = object.name;
// imagine doing this for a class with many attributes
return newInstance;
}
}
Finally, the Service code if necessary to better understand:
// imports
const URL = 'http://localhost:8000';
const baseConfig: AxiosRequestConfig = {
baseURL: URL,
headers: { 'Content-Type': 'application/json' },
withCredentials: true,
};
export const backend = axios.create({
...baseConfig,
baseURL: URL + '/someEndpoint',
});
export const Service = {
async getById(id: string): Promise<Class> {
try {
const { data } = await backend.get<Class>(`/${id}`);
return data;
} catch (err) {
throw new Error(err.response.data.message);
}
},
};
As I can't share the real code due to privacy, please let me know if this is enough or if more information is needed. Thanks in advance.
I thought it was some binding issue as here, but no.
So, I actually fixed this by updating the class validator in the back end, as the parsing was only necessary to parse the strings as number. But, by adding the annotation #Type(() => Number) to my dtos, I won't need to parse the strings anymore.
I have a aws lambda written in JS (TypeScript) which calls functions from different classes. I am getting undefined for the existingproducts variable even though it works fine when the REACT UI sends a call to the function. Below is my code using this keyword to reference the method of other class with current object in scope.
Entry point for the lambda
export const handler = async (upload: FileUpload, context: Context) => {
.....code .....
const parser = new ExcelValidator(new LookupService(), new ProductService());
const status = await parser.performExistingUpcValidation(products as Product[], upload, workbook);
return status
PerformExisitingUPCValidation
export class ExcelValidator {
constructor(public lookupService: LookupService, public productService: ProductService) {
}
async performExistingUpcValidation(products: Product[], upload: FileUpload, workbook?: Workbook): Promise<FileStatus> {
...code...
const existingProducts: any[] = await this.productService.getExistingProductsByUpcOrProductCode(productUpcs, productCodes);
console.log("This is the exisitingProduct", existingProducts)
}
ProductServiceClass
export class ProductService {
constructor() {
}
#Query(()=>[Product])
async getExistingProductsByUpcOrProductCode(#Arg("upcs", ()=> [String]) upcs: string[], #Arg("productCodes", ()=> [String]) productCodes: string[]): Promise<Product[]> {
console.log("I came here")
let query = `SELECT * from table
in (${upcs.join(",")})`;
if(productCodes.length){
query += ` OR "productCode" in ('${productCodes.join("','")}')`;
}
const results = await pool.snowflake?.execute(query);
return results as Product[];
}
After all the execution I am able to see
This is the exisitingProduct undefined
which means my execution does not reach the ProductServiceClass. Can someone point me to what is wrong or missing? Also any documentation/reference to read more will help alot.
My suggestion for you is review the item below:
const results = await pool.snowflake?.execute(query);
Are you confident that snowflake is not null, because you are accepting to be undefined with help of the question mark.
I create an authentication middleware in NestJs like below:
#Injectable()
export class AuthenticationMiddleware implements NestMiddleware {
constructor() {}
async use(req: any, res: any, next: () => void) {
const authHeaders = req.headers.authorization;
if (authHeaders) {
//some logic etc.
//req.user = user;
next();
} else {
throw new UnathorizedException();
}
}
}
... where I get from headers - an auth token, decode it and check if this user is correct and exists in database, if he exists then i set user object into req.user. And now I have a question, how to get this req.user in my services and use in business logic? I need to get id from req.user but I do not know how.
I know that I can do this by using #Req() request in controller parameters and pass this request into my function, but I do not want it, cause is (for me) a ugly practice. So, how to get this req.user into my services?
thanks for any help!
Well, to get the user in the service you have two options:
use #Req() in the controller and pass it, as you have mentioned
Make your service REQUEST scoped and inject the request object into the service
Personally, I'd go with the former, as request scoping has its own pros and cons to start weighing and dealing with (like not being able to use the service in a passport strategy or a cron job). You can also just make the user optional, or bundle it into the body or whatever is passed to the service and then have access to it without it being an explicit parameter.
You can create a decorator to do it. Something like this
current-user.decorator.ts
import { createParamDecorator, ExecutionContext } from '#nestjs/common';
export const CurrentUser = createParamDecorator(
(property: string, ectx: ExecutionContext) => {
const ctx = ectx.getArgByIndex(1);
return property ? ctx.req.user && ctx.req.user[property] : ctx.req.user;
},
);
example.controller.ts
#ApiTags('example')
#Controller('example')
export class ExampleController {
constructor(private readonly exampleService: ExampleService) {}
#Get('/')
public async doSomething(#CurrentUser() user: YourUserClassOrInteface,): Promise<any> {
return this.exampleService.exampleFunction(user.id);
}
}
example.service.ts
export class ExampleService {
constructor() {}
public async exampleFunction(id: string): Promise<void> {
console.log('id:', id);
return;
}
}
IMPORTANT: Injecting the Request in the services is not a good solution because it will make a new one in each endpoint request. That is why the Decorators are used. It will make it easy to work with needed data and do not hand over only the parameters that are needed instead of transferring the extra big request object.
Alternative solution(if you won't use request scoped injection): you can use async hooks. There is many libraries which simplify async hooks usage, like this one. You simply set your context in middleware:
#Injectable()
export class AuthenticationMiddleware implements NestMiddleware {
constructor() {}
async use(req: any, res: any, next: () => void) {
const authHeaders = req.headers.authorization;
if (authHeaders) {
//some logic etc.
//req.user = user;
Context.run(next, { user: req.user });
} else {
throw new UnathorizedException();
}
}
}
And then you can get user instance in any place in your code by simply calling Context.get()
You can define your own Request interface like this
import { Request } from 'express';
...
export interface IRequestWithUser extends Request {
user: User;
}
then just give the type of req parameter to IRequestWithUser.
I'm using typescript for my app node.js express.
I would like say the res.body is type personne.
I have tried this:
router.post('/',(req: Request, res: Response) => {
const defunt:PersoneModel = res.(<PersoneModel>body);
}
I have this model:
export type PersoneModel = mongoose.Document & {
nom: String,
prenom: String,
}
Can you help me?
Thank you.
Update:
As of #types/express#4.17.2, the Request type uses generics.
https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/express/index.d.ts#L107
interface Request<P extends core.Params = core.ParamsDictionary, ResBody = any, ReqBody = any, ReqQuery = core.Query> extends core.Request<P, ResBody, ReqBody, ReqQuery> { }
You could set the type of req.body to PersoneModel like this:
import { Request, Response } from 'express';
router.post('/',(req: Request<{}, {}, PersoneModel>, res: Response) => {
// req.body is now PersoneModel
}
For #types/express#4.17.1 and below
Encountered similar problem and I solved it using generics:
import { Request, Response } from 'express';
interface PersoneModel extends mongoose.Document {
nom: String,
prenom: String,
}
interface CustomRequest<T> extends Request {
body: T
}
router.post('/',(req: CustomRequest<PersoneModel>, res: Response) => {
// req.body is now PersoneModel
}
We can use as. This should be enough to imply that res.body is PersoneModel
const defunt = res.body as PersoneModel;
However more straightforward way is declaring type of the variable as a PersoneModel
const defunt: PersoneModel = res.body;
router.post('/',(req: Omit<Request,'body'> & { body: PersoneModel }, res: Response) => {
// code
}
this also will do, useful if you want to create abstration
Here is what worked for me (I am using node, express, and a postgres Pool connection):
import express, { Response, Request } from 'express';
export interface ITeamsRequest {
teams?: ITeam[];
divisions?: ITournamentDivision[];
}
export function setupTeams(app: express.Application, client: Pool) {
app.get(
'/teams',
async (req: Request, res: Response<ITeamsRequest>) => {
const teams = // get teams;
const divisions = // get divisions;
res.status(200);
return res.json({ teams, divisions });
},
);
}
The key thing is to manually import Request and Response, and using a type generic (Response<ITeamsRequest>) you can define your own ResBody type.
This was on express version 4.17.1 and #types/express 4.17.11