Skip to content

Instantly share code, notes, and snippets.

@umutyerebakmaz
Last active April 19, 2023 17:10
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save umutyerebakmaz/5e2c53cbeba3355988ea58e98ff7527d to your computer and use it in GitHub Desktop.
Save umutyerebakmaz/5e2c53cbeba3355988ea58e98ff7527d to your computer and use it in GitHub Desktop.
TypeORM many to many lazy relation jointable custom table name TypeGraphQL Example (with bi-directional conect GraphQL Approach)
import { Entity, PrimaryGeneratedColumn, Column, BaseEntity, ManyToMany } from 'typeorm';
import { ObjectType, Field, ID, InputType, ArgsType, Int } from 'type-graphql';
import { Book } from '../book/book.entity';
@Entity()
@ObjectType()
export class Author extends BaseEntity {
@PrimaryGeneratedColumn("uuid")
@Field(type => ID)
id: string;
@Column()
@Field()
title: string
@Column({ unique: true })
@Field()
slug: string
@Column()
@Field()
description: string
@Column()
@Field()
image: string
@Column()
@Field()
birth: string
@Column()
@Field()
death: string
@Column()
@Field()
translator: boolean
// book_authors
@ManyToMany(type => Book, book => book.authors, { lazy: true })
@Field(type => [Book])
books: Promise<Book[]>
}
@InputType()
export class AuthorInput {
@Field(type => ID)
id: string;
@Field()
title: string
@Field()
slug: string
@Field()
description: string
@Field()
image: string
@Field()
birth: string
@Field()
death: string
@Field()
translator: boolean
}
@InputType()
export class CreateAuthorInput {
@Field()
title: string
@Field()
slug: string
@Field()
description: string
@Field()
image: string
@Field()
birth: string
@Field()
death: string
@Field()
translator: boolean
}
@ArgsType()
export class AuthorsFilter {
@Field({ nullable: true })
title?: string;
@Field(type => Int, { nullable: true })
skip?: number;
@Field(type => Int, { nullable: true })
take?: number;
}
@ArgsType()
export class AuthorFilter {
@Field({ nullable: true })
id?: string;
@Field({ nullable: true })
title?: string;
@Field({ nullable: true })
slug?: string;
}
import { Resolver, Query, Args, Arg, Mutation, Authorized } from "type-graphql";
import { Author, AuthorsFilter, AuthorFilter, CreateAuthorInput } from './author.entity';
import { Like, getRepository } from 'typeorm';
import { GraphQLUpload } from 'graphql-upload';
import { createWriteStream } from 'fs';
import { Upload } from '../../types/Upload';
import * as path from 'path';
@Resolver(Author)
export class AuthorResolver {
@Query(returns => [Author])
async allAuthors(
@Args() { title, skip, take }: AuthorsFilter
): Promise<Author[]> {
if (title) {
return Author.find({ where: { title: Like(`${title}%`), skip, take } })
}
return Author.find({ skip, take });
}
@Query(returns => Author)
async author(
@Args() { id, title, slug }: AuthorFilter
): Promise<Author> {
if (id) {
return Author.findOne(id)
}
if (title) {
return Author.findOne(title)
}
if (slug) {
return Author.findOne(slug)
}
throw new Error('book not found');
};
@Mutation(returns => Author)
async createAuthor(
@Arg('author') input: CreateAuthorInput
): Promise<CreateAuthorInput> {
const author = new Author();
author.title = input.title;
author.slug = input.slug;
author.description = input.description;
author.image = input.image;
author.birth = input.birth;
author.death = input.death;
author.translator = input.translator;
await author.save();
return author;
}
@Mutation(returns => Author)
async updateAuthor(
@Arg('id') id: string,
@Arg('author') input: CreateAuthorInput
): Promise<CreateAuthorInput> {
const authorRepository = getRepository(Author);
const author = await authorRepository.findOne(id);
if (!author) {
throw new Error('author not found');
}
author.title = input.title;
author.slug = input.slug;
author.description = input.description;
author.image = input.image;
author.birth = input.birth;
author.death = input.death;
author.translator = input.translator;
await authorRepository.save(author);
return author;
}
@Authorized("ADMIN")
@Mutation(returns => Author)
async deleteAuthor(
@Arg('id') id: string
): Promise<Author> {
const authorRepository = getRepository(Author);
const author = await authorRepository.findOne(id);
if (!author) {
throw new Error('author not found!');
}
await authorRepository.delete(id);
return author;
}
@Mutation(returns => Boolean)
async createAuthorImage(@Arg('file', () => GraphQLUpload) file: Upload) {
const { filename, mimetype, createReadStream } = await file;
const acceptedTypes = ['image/jpeg', 'image/png'];
if (acceptedTypes.indexOf(mimetype) !== -1) {
const stream = createReadStream();
stream.pipe(createWriteStream(path.join(__dirname, `../../../images/authors/${filename}`)));
return true;
}
throw new Error('Unsupported image type.')
};
}
// postman operations
// {"query":"mutation CreateAuthorImage($file: Upload!) {\n createAuthorImage(file: $file)\n}\n"}
import { Entity, PrimaryGeneratedColumn, Column, BaseEntity, ManyToOne, ManyToMany, JoinTable} from 'typeorm';
import { ObjectType, Field, ID, Int, ArgsType, InputType } from 'type-graphql';
import { Publisher } from '../publisher/publisher.entity';
import { Author } from '../author/author.entity';
@Entity()
@ObjectType()
export class Book extends BaseEntity {
@PrimaryGeneratedColumn("uuid")
@Field(type => ID)
id: string;
@Column()
@Field()
title: string;
@Column({ unique: true })
@Field()
slug: string;
@Column()
@Field()
image: string;
@Column()
@Field()
description: string;
@Column()
@Field()
firstEditionYear: string;
@Column()
@Field()
edition: string;
@Column()
@Field()
numberOfPage: string;
@Column()
@Field()
isbn: string;
@Column()
@Field()
language: string;
@Column()
publisherId: string;
@ManyToOne(type => Publisher, { lazy: true })
@Field(type => Publisher)
publisher: Promise<Publisher>;
// book_authors
@ManyToMany(type => Author, author => author.books, { lazy: true })
@JoinTable({
name: "book_author", // table name for the junction table of this relation
joinColumn: {
name: "bookId",
referencedColumnName: "id"
},
inverseJoinColumn: {
name: "authorId",
referencedColumnName: "id"
}
})
@Field(type => [Author])
authors: Promise<Author[]>;
}
@InputType()
export class CreateBookInput {
@Field()
title: string;
@Field()
slug: string;
@Field()
image: string;
@Field()
description: string;
@Field()
firstEditionYear: string;
@Field()
edition: string;
@Field()
numberOfPage: string;
@Field()
isbn: string;
@Field()
language: string;
@Field()
publisherId: string;
}
@ArgsType()
export class BooksFilter {
@Field({ nullable: true })
title?: string;
@Field(type => Int, { nullable: true })
skip?: number;
@Field(type => Int, { nullable: true })
take?: number;
}
@ArgsType()
export class BookFilter {
@Field({ nullable: true })
id?: string;
@Field({ nullable: true })
title?: string;
@Field({ nullable: true })
slug?: string;
}
import { Resolver, Query, Args, Arg, Mutation, Authorized } from "type-graphql";
import { Book, BookFilter, BooksFilter, CreateBookInput } from './book.entity';
import { Like, getRepository } from 'typeorm';
import { GraphQLUpload } from 'graphql-upload';
import { createWriteStream } from 'fs';
import { Upload } from '../../types/Upload';
import * as path from 'path';
import { Author, AuthorInput } from '../author/author.entity';
@Resolver(Book)
export class BookResolver {
@Query(returns => [Book])
async allBooks(
@Args() { title, skip, take }: BooksFilter
): Promise<Book[]> {
if (title) {
return Book.find({ where: { title: Like(`${title}%`) }, skip, take });
}
return Book.find({ skip, take });
}
@Query(returns => Book)
async book(
@Args() { id, title, slug }: BookFilter
): Promise<Book> {
if (id) {
return Book.findOne(id)
}
if (title) {
return Book.findOne(title)
}
if (slug) {
return Book.findOne(slug)
}
throw new Error('book not found');
};
@Mutation(returns => Book)
async createBook(
@Arg('book') input: CreateBookInput,
@Arg('authors', type => [AuthorInput]) authors: Author[]
): Promise<CreateBookInput> {
const book = new Book();
book.title = input.title;
book.slug = input.slug;
book.image = input.image;
book.description = input.description;
book.firstEditionYear = input.firstEditionYear;
book.edition = input.numberOfPage;
book.numberOfPage = input.numberOfPage;
book.isbn = input.isbn;
book.language = input.language;
book.publisherId = input.publisherId;
book.authors = Promise.resolve(authors);
await book.save();
return book;
}
@Mutation(returns => Book)
async updateBook(
@Arg('id') id: string,
@Arg('book') input: CreateBookInput
): Promise<CreateBookInput> {
const bookRepository = getRepository(Book);
const book = await bookRepository.findOne(id);
if (!book) {
throw new Error('book not found');
}
book.title = input.title;
book.slug = input.slug;
book.image = input.image;
book.description = input.description;
book.firstEditionYear = input.firstEditionYear;
book.edition = input.edition;
book.numberOfPage = input.numberOfPage;
book.isbn = input.isbn;
book.language = input.language;
book.publisherId = input.publisherId;
await bookRepository.save(book);
return book;
}
@Authorized("ADMIN")
@Mutation(returns => Book)
async deleteBook(
@Arg('id') id: string
): Promise<Book> {
const bookRepository = getRepository(Book);
const book = await bookRepository.findOne(id);
if (!book) {
throw new Error('book not found!')
}
await bookRepository.delete({ id });
return book;
}
@Mutation(returns => Boolean)
async createBookImage(@Arg('file', () => GraphQLUpload) file: Upload) {
const { filename, mimetype, createReadStream } = await file;
const acceptedTypes = ['image/jpeg', 'image/png'];
if (acceptedTypes.indexOf(mimetype) !== -1) {
const stream = createReadStream();
stream.pipe(createWriteStream(path.join(__dirname, `../../../images/books/${filename}`)));
return true;
}
throw new Error('Unsupported image type.')
};
}
// postman operations
// {"query":"mutation CreateBookImage($file: Upload!) {\n createBookImage(file: $file)\n}\n"}
@adsee42
Copy link

adsee42 commented Oct 5, 2020

Very helpful!! Thanks!!

BTW, I saw a mix of Book.findOne and bookRepository.findOne. What's the difference?

@lebowvsky
Copy link

Thanks!!! As adsee42 said "Very helpful!!" !! :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment