I am creating a reddit clone, using a type-graphql, express-graphql and typegoose,
first i run a query for posts and i get the user info (_id, username) :
query posts {
posts {
_id
title
image
body
user {
_id
username
}
subredditList {
topic
_id
}
createdAt
}
}
but when i run query for subreddit to get all posts that belong to that subreddit, i get a user is null
the query i run for subreddit:
query subreddit($topic: String!) {
subreddit(input: { topic: $topic }) {
_id
topic
posts {
_id # works fine
title # works fine
body # works fine
image # works fine
# the user is null !!, i get user: null
user {
_id
username
}
createdAt
}
}
}
I get user: null even when the posts field in Subreddit object type is resolved
Subreddit object type:
#ObjectType()
export class Subreddit {
#Field(() => String)
_id: string;
#Field(() => String)
#prop({ required: true })
topic: string;
#Field(() => [Post])
posts: Post[]; // i get all info exept the user object
#Field(() => String)
createdAt: string;
#Field(() => String)
updatedAt: string;
}
Subreddit resolver:
#Resolver(() => Subreddit, {})
export default class SubredditResolver {
constructor(private readonly subredditService: SubredditService) {
this.subredditService = new SubredditService();
}
#Query(() => [Subreddit])
async subreddits() {
return this.subredditService.subreddits();
}
#Query(() => Subreddit, { nullable: true })
async subreddit(#Arg("input") input: GetSubredditByTopic) {
return this.subredditService.subreddit(input);
}
#Mutation(() => Subreddit)
async createSubreddit(
#Arg("input") input: CreateSubredditInput
): Promise<Subreddit> {
return this.subredditService.createSubreddit(input);
}
// here where i resolve the "posts" field in my Subreddit object type
#FieldResolver(() => Post, {})
async posts(#Root() subreddit: Subreddit) {
console.log(args);
return this.subredditService.posts(subreddit);
}
}
and finally the resolver function is like this:
async posts(input: Subreddit) {
try {
const posts = await PostModel.find({ subredditId: input._id });
return posts;
} catch (err) {
console.log(err);
throw new Error("Couldn't get the posts");
}
}
Here is my Post object type looks like:
#ObjectType()
export class Post {
#Field(() => String)
_id: string;
#Field(() => String)
#prop({ required: true })
title: string;
#Field(() => String)
#prop({ required: true })
body: string;
#Field(() => String, { nullable: true })
#prop({})
image: string;
#Field(() => String)
#prop({ ref: () => Subreddit })
subredditId: Ref<Subreddit>;
#Field(() => [Subreddit])
subredditList: [Subreddit];
#Field(() => [Comment])
comments: Comment[];
// note that the "user" field is string refers to id of the user,
#Field(() => User, { nullable: true })
#prop({ ref: () => User })
user: Ref<User>;
#Field(() => Date)
createdAt: Date;
#Field(() => Date)
updatedAt: Date;
}
Post resolver:
#Resolver(() => Post)
export default class PostResolver {
constructor(private readonly postService: PostService) {
this.postService = new PostService();
}
#Query(() => [Post])
async posts() {
return this.postService.posts();
}
#Authorized()
#Mutation(() => Post)
async createPost(
#Arg("input") input: CreatePostInput,
#Ctx() context: Context
): Promise<Post> {
return this.postService.createPost(input, context);
}
#FieldResolver()
async subredditList(#Root() post: Post) {
return this.postService.subredditList(post);
}
// here i resolve the user field in my Post object type, and it works fine
#FieldResolver()
async user(#Root() post: Post) {
return this.postService.user(post);
}
}
the resolver function for "user" field in Post object type:
async user(input: Post) {
try {
const user = await UserModel.findOne({
_id: input.user,
}).lean();
return user;
} catch (err) {
console.log(err);
throw new Error("Couldn't get the user");
}
}
the property "user" from post query is resolved but when i run subreddit query i get user is null .
any help please ?
thanks in advance.
Your problem seems to be that you are missing setting the type for arrays, like:
class YourClass {
// ...
#Field(() => [Post])
posts: Post[];
// ...
}
but it would need to be like:
class YourClass {
// ...
#Field(() => [Post])
#prop({ type: () => [Post] })
posts: Post[];
// ...
}
see Quick-Start-Guide
type has to be defined when working with Arrays, because Reflection only returns basic information. Look here for why
Like public: string[] is in reflection only Array.
or the note in #prop: Array Options
Option type must be provided, otherwise the array will result in Mixed read typegoose issue #300 for more info
Note: i dont know if anything is wrong with your graphql setup
i also read in the comments that there is supposedly a _doc property, which is internal in mongoose and should never be shown, but it gets exposed instead of properly translated due to a problem with class-transformer, the only workaround is to use #Exclude() for the whole class (or globally) and only #Expose() the proper properties, also see Integration Examples: Using with class-transformer
Related
I'm developing a register function with Typescript, but for some reason the method find couldn't search into database.
This is the model(users.ts):
interface UserAttributes {
id: number;
first_name: string;
last_name: string;
password: string;
email:string;
token: string;
description?: string;
createdAt?: Date;
updatedAt?: Date;
deletedAt?: Date;
}
'use strict';
const {
Model, Optional
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class Users extends Model<UserAttributes> {
public id!: number;
public first_name!: string;
public last_name!:string;
public email!: string;
public password!: string;
public token!: string;
// timestamps!
public readonly createdAt!: Date;
public readonly updatedAt!: Date;
public readonly deletedAt!: Date;
static associate(models) {
// define association here
}
}
Users.init({
id: {
type:DataTypes.INTEGER,
primaryKey:true
},
password: DataTypes.STRING,
first_name: DataTypes.STRING,
last_name: DataTypes.STRING,
token: DataTypes.STRING,
}, {
sequelize,
modelName: 'Users',
tableName:'users',
});
return Users;
};
This is the register services(Services.ts):
const register: typeof UserAttributes = (first_name, last_name, email, password) => {
return Users.findByPk(email).then(userExist => {
if(userExist) {
console.log('User already register. Please Login')
}
}).then(bcrypt => {
return bcrypt.hash(password, 10);
}).then(hash => {
Users.create({first_name,last_name, email, password: hash})
}).then(user => {
return user;
}).catch((error) => {
throw error;
})
}
And return me the following error message at the moment to make http request with postman:
return Users.findOne({ where: { email: email } }).then(function (userExist)
^
TypeError: Cannot read properties of undefined (reading 'findOne')
I dont think it is due a sequelize or progress setup's errors. Because I can make sucesfully's migrations into database. The problem is when I test the controller
This is the controller(controller.ts):
const userServices = require('./Services');
async function registerController (req:Request, res:Response): Promise<Response>{
await userServices.register(req.body).then(() => {
res.status(200).send('User created succesfully');
}).catch((error) => {
return res.status(500).send(error);
})
}
So, If anyone could bring me some help I'd really be gratefull
I have 4 Entities that I set up and I want to save a recipe.
#Entity('recipes')
export class Recipe extends BaseEntity {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column({ type: String, unique: true })
recipe_name: string;
#Column({ type: String })
description: string;
#OneToMany(() => RecipeIngredientList, (r: RecipeIngredientList) => r.recipeId)
recipeLists: RecipeIngredientList[];
}
#Entity('measurements')
export class MeasurementUnits {
#PrimaryGeneratedColumn()
id: number;
#Column({ type: String })
unit: string;
#ManyToMany(() => RecipeIngredientList, (r) => r.unitID, { cascade: true })
recipeList: RecipeIngredientList[];
}
#Entity('ingredients')
export class Ingredient {
#PrimaryGeneratedColumn()
id: number;
#Column({ type: String })
ingredient_name: string;
#ManyToMany(() => RecipeList, (r: RecipeList) => r.ingredientID, { cascade: true })
recipeList: RecipeList[];
}
#Entity('RecipeIngredientList')
export class RecipeIngredientList{
#PrimaryGeneratedColumn()
id: number;
#Column({type:Number})
amount: number;
#ManyToOne(() => Recipe, (r) => r.recipeLists)
recipeID: Recipe;
#ManyToMany(() => Ingredient)
#JoinTable()
ingredientID: Ingredient[];
#ManyToMany(() => MeasurementUnits)
#JoinTable()
unitID: MeasurementUnits[];
}
//My Save Method() The getDatabaseConnention() is in a service I wrote to
// check/connect via the DataSource.manager
const create = async (recipe: Recipe): Promise<Recipe> => {
const recipeRepository = await (
await getDatabaseConnection()).getRepository(Recipe);
const newRecipe: Recipe = await recipeRepository.save(recipe)
.catch((e) => {
console.debug('failed to create recipe Record', e);
throw new Error(e);
});
return newRecipe;
};
The effect of this is its creating the 6 tables in Postgres as expected but its only saving to the main recipes table and not able to save any of the relations. Am I missing foreignKeys or constraints in my entity setup? Or is my save method not properly identifying the relationships?
Now I know the way I have the DB setup the RecipeIngredientList Table is going to be the work horse and has a potential to get bloated, but I think this is a decent setup for a recipe management (I removed a lot of metadata to not bloat the post).
I am pretty new in unit testing, I want to test my application that is written in node, the technologies I use are express.js, type-graphql, and type-goose, I searched a lot on the web to find some articles to help me about how to do unit testing for a resolver that is written in type-graphql or how to do unit testing for a schema that is written in type-goose but unfortunately, I couldn't find any, now please help me solve the problem and at least pass me some useful links
this is the input:
import { Field, InputType } from 'type-graphql'
#InputType()
export class AdminLoginInput {
#Field(() => String)
email: string
#Field(() => String)
password: string
}
This is the Resolver:
import { Arg, Ctx, Mutation, Query, Resolver } from 'type-graphql'
import { AdminLoginInput } from '../../Inputs/Admins/admin.input'
import { Admin } from '../../schema/Admins/admin.schema'
import AdminService from '../../service/Admin/admin.service'
import { IContext } from '../../../interface/context'
#Resolver()
export default class AdminResolver {
constructor(private adminService: AdminService) {
this.adminService = new AdminService()
}
#Mutation(() => String)
loginAdmin(#Arg('input') input: AdminLoginInput, #Ctx() context: IContext) {
return this.adminService.loginAdmin(input, context)
}
#Query(() => Admin, { nullable: true })
meAdmin(#Ctx() context: IContext) {
return context.admin
}
}
this is the schema
import { ObjectType, Field } from 'type-graphql'
import { getModelForClass, prop, pre, index } from '#typegoose/typegoose'
import bcrypt from 'bcrypt'
#pre<Admin>('save', async function () {
if (!this.isModified('password')) {
return
}
const salt = await bcrypt.genSalt(10)
const hash = await bcrypt.hashSync(this.password, salt)
this.password = hash
})
#ObjectType()
export class Admin {
#Field(() => String, { nullable: true })
_id: string
#Field(() => String)
#prop()
firstname: string
// #Field(() => String)
// #prop()
// name: string
#Field(() => String)
#prop({ unique: true })
email: string
#Field(() => String, { nullable: true })
#prop()
gender: string
#Field(() => String, { nullable: true })
#prop()
language: string
#Field(() => String, { nullable: true })
#prop({ default: Date.now() })
created: string
#Field(() => String, { nullable: true })
#prop()
lastlogin: string
#Field(() => String, { nullable: true })
#prop()
updated: string
#prop()
password: string
#Field(() => [String], { nullable: true })
#prop()
messages: [string]
}
export const AdminModel = getModelForClass<typeof Admin>(Admin)
This is the service:
import { ApolloError } from 'apollo-server-express'
import bcrypt from 'bcrypt'
import { AdminLoginInput } from '../../Inputs/Admins/admin.input'
import { AdminModel } from '../../schema/Admins/admin.schema'
import { IContext } from '../../../interface/context'
import { signJwt, verifyJwt } from '../../../../scripts/jwt'
import nodemailer from 'nodemailer'
class AdminService {
async loginAdmin(input: AdminLoginInput, context: IContext) {
try {
const error = 'Invalid Email or Password'
const admin: any = await AdminModel.findOne({ email: input.email }).lean()
if (!admin) {
throw new ApolloError(error)
}
const passwordIsValid = await bcrypt.compare(
input.password,
admin.password,
)
if (!passwordIsValid) {
throw new ApolloError(error)
}
const token = signJwt({ userId: admin._id.toString() })
context.res.cookie('accessToken', token, {
// domain: process.env.CLIENT_URL,
// path: '/',
sameSite: 'none',
httpOnly: true,
secure: true,
// process.env.NODE_ENV === 'production' ||
// process.env.NODE_ENV === 'staging',
maxAge: 3.154e10,
})
return token
} catch (error) {
console.log(error)
}
}
}
export default AdminService
a full image and its folder structure
a complete image that contains files and folder structure
I am trying to build a backend with express.js. I now have the following problem:
import { Request, Response } from "express";
import { getManager } from "typeorm";
import { User } from "../entity/user.entity";
export const GetUser = async (req: Request, res: Response) => {
const repository = getManager().getRepository(User);
const { password, ...user } = await repository.findOne(req.params.id);
res.send(user);
};
The following error always occurs:
(parameter) req: Request<ParamsDictionary, any, any, QueryString.ParsedQs, Record<string, any>>
Type 'string' has no properties in common with type 'FindOneOptions'.ts(2559)
router.ts
router.get("/api/users/:id", AuthMiddleware, GetUser);
user.entity.ts
import {
Column,
Entity,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
} from "typeorm";
import { Role } from "./role.entity";
#Entity()
export class User {
#PrimaryGeneratedColumn()
id: number;
#Column()
first_name: string;
#Column()
last_name: string;
#Column({
unique: true,
})
email: string;
#Column()
password: string;
#ManyToOne(() => Role)
#JoinColumn({ name: "role_id" })
role: Role;
}
Can anyone help me with my problem?
The typeorm's findOne function is equivalent to Select * ... limit 1. This means that the findOne actually expects an object containing conditions to match and return the first element for which the condition is satisfied.
In your case, the code should be:
repository.findOne({where: {id: parseInt(req.params.id, 10)}})
or
repository.findOneBy({id: parseInt(req.params.id, 10)})
This will find the user whose id field will match match the req.params.id.
according to manual, use the { criteria: search }
so
findOneBy({id: req.params.id});
I'm looking for the best way to update a User entity with typeorm and express.
I have something like this (I have reduced, but there are many other attributes) :
class User {
id: string;
lastname: string;
firstname: string;
email: string;
password: string;
isAdmin: boolean;
}
And I have a path to update user's attributes like :
app.patch("/users/me", ensureAuthenticated, UserController.update);
Now (and it's my question), how to properly update the user? I don't want the user to be able to add themselves as an admin.
So I have :
export const update = async (req: Request, res: Response) => {
const { sub } = res.locals.token;
const { lastname, firstname, phone, email } = req.body;
const userRepository = getRepository(User);
const currentUser = await userRepository.findOne({ id: sub });
const newUserData: User = {
...currentUser,
lastname: lastname || currentUser.lastname,
firstname: firstname || currentUser.firstname,
phone: phone || currentUser.phone,
email: email || currentUser.email
};
await userRepository
.update({ id: sub }, newUserData)
.then(r => {
return res.status(204).send();
})
.catch(err => {
logger.error(err);
return res.status(500).json({ error: "Error." });
});
};
With that, I'm sure to update the right attributes, and the user can't update admin.
But I find it very verbose to create a new object and fill in the informations.
Do you have a better way to do it?
Thanks.
You can follow the DTO pattern.
For example:
class UserDto {
// define properties
constructor(data: any) {
// validate data
...
}
}
const userDto = new UserDto(req.body);
await userRepository
.update({ id: sub }, userDto)
.then(r => {
...
})
.catch(err => {
...
});
The nestjs framework has a good example of how you can set up a DTO pattern generically. But it's easy enough to roll your own using class-validator and class-transformer which will make the validation a lot more declarative.