nexus-typeorm-plugin
v0.0.1-alpha.12
Published
Create schemas from TypeORM models easily with this nexus plugin. Comes with dataloader!
Downloads
7
Maintainers
Readme
nexus-typeorm-plugin
Create a GraphQL.js schema from your TypeORM Entities.
Usage
Here's example Apollo Server with a posts
resolver to paginate over the Post
entity.
import 'reflect-metadata'
import * as path from 'path'
import dotenv from 'dotenv'
import { ApolloServer } from 'apollo-server'
import {
Column,
ManyToOne,
OneToMany,
PrimaryGeneratedColumn,
createConnection,
ManyToMany,
} from 'typeorm'
import {
NexusEntity,
nexusTypeORMPlugin,
entityType,
FindManyResolveFnContext,
} from 'nexus-typeorm-plugin'
import { queryType, makeSchema } from 'nexus'
import { propertyPathToAlias } from 'nexus-typeorm-plugin/dist/query-builder'
dotenv.config()
@NexusEntity()
export class User {
@PrimaryGeneratedColumn()
public id!: number
@Column()
public name: string
@Column()
public age: number
@OneToMany(() => Post, post => post.author)
public posts: Post[]
@ManyToMany(() => Category, category => category.posts)
public categories?: Category[]
constructor(name: string, age: number, posts: Post[], categories?: Category[]) {
this.name = name
this.age = age
this.posts = posts
this.categories = categories
}
}
@NexusEntity()
class Category {
@PrimaryGeneratedColumn()
public id!: number
@Column()
public name: string
@ManyToMany(() => Post, post => post.categories)
public posts?: Post[]
constructor(name: string, posts?: Post[]) {
this.name = name
this.posts = posts
}
}
@NexusEntity()
class Post {
@PrimaryGeneratedColumn()
public id!: number
@Column()
public title: string
@ManyToOne(() => User, user => user.posts)
public author: User
@ManyToMany(() => Category, category => category.posts)
public categories?: Category[]
constructor(title: string, author: User, categories?: Category[]) {
this.title = title
this.author = author
this.categories = categories
}
}
const { DB_HOST, DB_TYPE, DB_NAME, DB_USERNAME, DB_PASSWORD, DB_PORT } = process.env
async function main() {
await createConnection({
entities: [User, Post, Category],
host: DB_HOST,
type: DB_TYPE as 'postgres',
database: DB_NAME,
username: DB_USERNAME,
password: DB_PASSWORD,
port: DB_PORT ? parseInt(DB_PORT as any, 10) : undefined,
synchronize: true,
})
const query = queryType({
definition: t => {
t.crud.posts()
t.crud.users('listUsers')
t.crud.users('listUsersWithNameJohn', {
resolve: (ctx: FindManyResolveFnContext<User, User, any, any>) => {
ctx.args.where = {
...ctx.args.where,
name: 'John',
}
return ctx.next(ctx)
},
})
t.crudField('listPostsWithCategoryFoo', {
entity: 'Post',
type: 'Post',
method: 'findMany',
resolve: ctx => {
return ctx.next({
...ctx,
queryBuilderConfig: config => ({
...config,
joins: [
...(config.joins || []),
{
type: 'inner',
select: false,
propertyPath: 'categories',
where: {
expression: `${propertyPathToAlias('categories')}.id = :id`,
params: { id: 1 },
},
},
],
}),
})
},
})
},
})
const schema = makeSchema({
types: [nexusTypeORMPlugin(), query, entityType(User), entityType(Post), entityType(Category)],
outputs: {
schema: path.resolve('generated', 'schema.graphql'),
typegen: path.resolve('generated', 'nexus-typegen.ts'),
},
})
new ApolloServer({ schema }).listen(3000)
console.log('Server running at http://localhost:3000')
}
main().catch(error => {
// eslint-disable-next-line no-console
console.error(error)
process.exit(1)
})
Features
Entity field definition
Helps you create an objectType
for an entity faster and simpler.
export const User = entityType<User>(User, {
definition: t => {
t.entity.id()
// Either t.entityField('name') or
t.entity.name()
// Either t.crudField('followers', { entity: 'UserFollow', ... or
t.crud.userFollows('followers', {
type: 'User',
resolve: async (source: User, args, ctx, info, next) => {
const follows = await next(source, args, ctx, info)
return getConnection()
.getRepository(User)
.createQueryBuilder()
.where('id IN (:...ids)', {
ids: follows.map((follow: UserFollows) => follow.followerId),
})
.getMany()
},
})
},
})
CRUD
Find Many
Creates a field that resolves into a list of instances of the choosen entity. It includes the first
, last
, after
, before
, skip
, where
, and the orderBy
arguments.
export const Query = queryType({
definition(t) {
t.crud.posts()
t.crud.users('listUsers')
t.crud.users('listUsersWithNameJohn', {
resolve: ctx => {
ctx.args.where = {
...ctx.args.where,
name: 'John',
}
return ctx.next(ctx)
},
})
t.crudField('listPostsWithCategoryFoo', {
entity: 'Post',
method: 'findMany',
resolve: ctx => {
return ctx.next({
...ctx,
queryBuilderConfig: config => ({
...config,
joins: [
...config.joins,
{
type: 'inner',
select: false,
propertyPath: 'categories',
where: {
expression: `${propertyPathToAlias('categories')}.id = :id`,
params: { id: 1 },
},
},
],
}),
})
},
})
},
})
Find One
Creates a field that resolves into one entity instance. It includes the where
and the orderBy
arguments.
export const Query = queryType({
definition(t) {
t.crud.user()
t.crud.post()
t.crud.post('postsByUserId', {
args: args => ({ ...args, userId: stringArg({ nullable: false }) }),
resolve: ctx =>
ctx.next({
...ctx,
args: { ...ctx.args, where: { ...ctx.args.where, userId: ctx.args.userId } },
}),
})
},
})
Create One
Creates a field that resolves into one entity instance. It includes the where
and the orderBy
arguments.
export const Mutation = mutationType({
definition(t) {
t.crud.createOneUser()
t.crud.createOnePost()
},
})
Example
mutation {
createOneUser(
data: {
name: "John with posts"
age: 42
type: NORMAL
profile: { create: { slug: "john-with-posts", displayName: "displayName" } }
posts: {
create: [
{
title: "created post"
isPublic: false
categories: { create: [{ name: "create category 1" }, { name: "create category 2" }] }
}
{ title: "created post 2", isPublic: true }
]
}
}
) {
id
name
age
profile {
slug
displayName
}
posts {
title
isPublic
categories {
name
}
}
}
}
Add business logic
export const Mutation = mutationType({
definition(t) {
t.crud.createOneUser('addUser', {
args: {
name: stringArg({ nullable: false }),
postsIds: stringArg({ list: true, nullable: false }),
},
resolve: ctx => {
const { name, postsIds } = ctx.args
return ctx.next({
...ctx,
args: {
data: {
name,
posts: {
connect: { id_in: postsIds },
},
},
},
})
},
})
},
})
Auto join
In order to speed up requests and decrease the number of queries made to the database, this plugin analyzes each graphql query and makes the necessary joins automatically.
{
user {
id
posts {
id
}
}
}
Generates a SQL query that left joins Post.
SELECT * from User ... LEFT JOIN Post
Checkout the tests/
directory to see examples.
Contributing
To run tests create .env
file at the project root and fill it with database information.
TEST_DB_HOST=localhost
TEST_DB_TYPE=mysql
TEST_DB_NAME=test
TEST_DB_USERNAME=root
TEST_DB_PASSWORD=mypassword
TEST_DB_PORT=3316
If you want, you can run a Docker container of MySQL for test based on .env
file.
docker-compose up -d
Now you can run tests.
yarn test