Skip to content

Commit

Permalink
docs(examples): add Typegoose integration example
Browse files Browse the repository at this point in the history
  • Loading branch information
MichalLytek committed Mar 24, 2019
1 parent be55e3c commit c03e70d
Show file tree
Hide file tree
Showing 19 changed files with 1,189 additions and 7 deletions.
7 changes: 4 additions & 3 deletions dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ require("ts-node/register/transpile-only");
// require("./examples/generic-types/index.ts");
// require("./examples/interfaces-inheritance/index.ts");
// require("./examples/middlewares/index.ts");
// require("./examples/query-complexity/index.ts");
// require("./examples/redis-subscriptions/index.ts");
// require("./examples/resolvers-inheritance/index.ts");
// require("./examples/simple-subscriptions/index.ts");
// require("./examples/query-complexity/index.ts");
// require("./examples/simple-usage/index.ts");
// require("./examples/using-container/index.ts");
// require("./examples/using-scoped-container/index.ts");
// require("./examples/typegoose/index.ts");
// require("./examples/typeorm-basic-usage/index.ts");
// require("./examples/typeorm-lazy-relations/index.ts");
// require("./examples/using-container/index.ts");
// require("./examples/using-scoped-container/index.ts");
1 change: 1 addition & 0 deletions docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ All examples has a `examples.gql` file with sample queries/mutations/subscriptio

- [TypeORM (manual, synchronous) \*](https://github.com/19majkel94/type-graphql/tree/master/examples/typeorm-basic-usage)
- [TypeORM (automatic, lazy relations) \*](https://github.com/19majkel94/type-graphql/tree/master/examples/typeorm-lazy-relations)
- [Typegoose](https://github.com/19majkel94/type-graphql/tree/master/examples/typegoose)
- [Apollo Engine (Apollo Cache Control) \*\*](https://github.com/19majkel94/type-graphql/tree/master/examples/apollo-engine)

_\* Note that you need to edit the TypeORM examples `index.ts` with credentials to your local database_
Expand Down
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ All examples has a `examples.gql` file with sample queries/mutations/subscriptio

- [TypeORM (manual, synchronous) \*](./typeorm-basic-usage)
- [TypeORM (automatic, lazy relations) \*](./typeorm-lazy-relations)
- [Typegoose](./typegoose)
- [Apollo Engine (Apollo Cache Control) \*\*](./apollo-engine)

_\* Note that you need to edit the TypeORM examples `index.ts` with credentials to your local database_
Expand Down
19 changes: 19 additions & 0 deletions examples/typegoose/entities/rate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { ObjectType, Field, Int } from "../../../src";
import { prop as Property, Ref } from "typegoose";

import { User } from "./user";

@ObjectType()
export class Rate {
@Field(type => Int)
@Property({ required: true })
value: number;

@Field()
@Property({ default: new Date(), required: true })
date: Date;

@Field(type => User)
@Property({ ref: User, required: true })
user: Ref<User>;
}
30 changes: 30 additions & 0 deletions examples/typegoose/entities/recipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { prop as Property, arrayProp as ArrayProperty, Typegoose, Ref } from "typegoose";
import { ObjectId } from "mongodb";
import { Field, ObjectType } from "../../../src";

import { Rate } from "./rate";
import { User } from "./user";

@ObjectType()
export class Recipe extends Typegoose {
@Field()
readonly _id: ObjectId;

@Field()
@Property({ required: true })
title: string;

@Field({ nullable: true })
@Property()
description?: string;

@Field(type => [Rate])
@ArrayProperty({ items: Rate, default: [] })
ratings: Rate[];

@Field(type => User)
@Property({ ref: User, required: true })
author: Ref<User>;
}

export const RecipeModel = new Recipe().getModelForClass(Recipe);
21 changes: 21 additions & 0 deletions examples/typegoose/entities/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { prop as Property, Typegoose } from "typegoose";
import { ObjectId } from "mongodb";
import { Field, ObjectType } from "../../../src";

@ObjectType()
export class User extends Typegoose {
@Field()
readonly _id: ObjectId;

@Field()
@Property({ required: true })
email: string;

@Field({ nullable: true })
@Property()
nickname?: string;

@Property({ required: true })
password: string;
}
export const UserModel = new User().getModelForClass(User);
57 changes: 57 additions & 0 deletions examples/typegoose/examples.gql
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
query GetRecipes {
recipes {
_id
title
author {
email
}
ratings {
value
}
}
}

query GetRecipe {
# fill with correct ObjectId
recipe(recipeId: "5c97eecbf49a072a00048ac5") {
_id
title
ratings {
value
user {
nickname
}
date
}
author {
_id
nickname
email
}
}
}

mutation AddRecipe {
addRecipe(recipe: { title: "New Recipe" }) {
_id
ratings {
value
}
author {
nickname
}
}
}

mutation RateRecipe {
# fill with correct ObjectId
rate(rate: { recipeId: "5c97eecbf49a072a00048ac5", value: 4 }) {
_id
ratings {
value
user {
email
}
}
}
}
34 changes: 34 additions & 0 deletions examples/typegoose/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Recipe, RecipeModel } from "./entities/recipe";
import { User, UserModel } from "./entities/user";

export async function seedDatabase() {
const defaultUser = await new UserModel({
email: "test@github.com",
nickname: "19majkel94",
password: "s3cr3tp4ssw0rd",
} as User).save();

await RecipeModel.create([
{
title: "Recipe 1",
description: "Desc 1",
author: defaultUser._id,
ratings: [
{ value: 2, user: defaultUser._id },
{ value: 4, user: defaultUser._id },
{ value: 5, user: defaultUser._id },
{ value: 3, user: defaultUser._id },
{ value: 4, user: defaultUser._id },
],
},
{
title: "Recipe 2",
author: defaultUser._id,
ratings: [{ value: 2, user: defaultUser }, { value: 4, user: defaultUser }],
},
] as Recipe[]);

return {
defaultUser,
};
}
52 changes: 52 additions & 0 deletions examples/typegoose/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import "reflect-metadata";
import { ApolloServer } from "apollo-server";
import { connect } from "mongoose";
import { ObjectId } from "mongodb";
import * as path from "path";
import { buildSchema } from "../../src";

import { RecipeResolver } from "./resolvers/recipe-resolver";
import { RateResolver } from "./resolvers/rate-resolver";
import { User } from "./entities/user";
import { seedDatabase } from "./helpers";
import { TypegooseMiddleware } from "./typegoose-middleware";
import { ObjectIdScalar } from "./object-id.scalar";

export interface Context {
user: User;
}

async function bootstrap() {
try {
// create mongoose connection
const mongoose = await connect("mongodb://localhost:27017/type-graphql");

// clean and seed database with some data
await mongoose.connection.db.dropDatabase();
const { defaultUser } = await seedDatabase();

// build TypeGraphQL executable schema
const schema = await buildSchema({
resolvers: [RecipeResolver, RateResolver],
emitSchemaFile: path.resolve(__dirname, "schema.gql"),
// use document converting middleware
globalMiddlewares: [TypegooseMiddleware],
// use ObjectId scalar mapping
scalarsMap: [{ type: ObjectId, scalar: ObjectIdScalar }],
});

// create mocked context
const context: Context = { user: defaultUser };

// Create GraphQL server
const server = new ApolloServer({ schema, context });

// Start the server
const { url } = await server.listen(4000);
console.log(`Server is running, GraphQL Playground available at ${url}`);
} catch (err) {
console.error(err);
}
}

bootstrap();
19 changes: 19 additions & 0 deletions examples/typegoose/object-id.scalar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { GraphQLScalarType, Kind } from "graphql";
import { ObjectId } from "mongodb";

export const ObjectIdScalar = new GraphQLScalarType({
name: "ObjectId",
description: "Mongo object id scalar type",
parseValue(value: string) {
return new ObjectId(value); // value from the client input variables
},
serialize(value: ObjectId) {
return value.toHexString(); // value sent to the client
},
parseLiteral(ast) {
if (ast.kind === Kind.STRING) {
return new ObjectId(ast.value); // value from the client query
}
return null;
},
});
12 changes: 12 additions & 0 deletions examples/typegoose/resolvers/rate-resolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Resolver, FieldResolver, Root } from "../../../src";

import { Rate } from "../entities/rate";
import { User, UserModel } from "../entities/user";

@Resolver(of => Rate)
export class RateResolver {
@FieldResolver()
async user(@Root() rate: Rate): Promise<User> {
return (await UserModel.findById(rate.user))!;
}
}
62 changes: 62 additions & 0 deletions examples/typegoose/resolvers/recipe-resolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { ObjectId } from "mongodb";
import { Resolver, Query, FieldResolver, Arg, Root, Mutation, Ctx } from "../../../src";

import { Recipe, RecipeModel } from "../entities/recipe";
import { Rate } from "../entities/rate";
import { User, UserModel } from "../entities/user";
import { RecipeInput } from "./types/recipe-input";
import { RateInput } from "./types/rate-input";
import { Context } from "../index";
import { ObjectIdScalar } from "../object-id.scalar";

@Resolver(of => Recipe)
export class RecipeResolver {
@Query(returns => Recipe, { nullable: true })
recipe(@Arg("recipeId", type => ObjectIdScalar) recipeId: ObjectId) {
return RecipeModel.findById(recipeId);
}

@Query(returns => [Recipe])
async recipes(): Promise<Recipe[]> {
return await RecipeModel.find({});
}

@Mutation(returns => Recipe)
async addRecipe(
@Arg("recipe") recipeInput: RecipeInput,
@Ctx() { user }: Context,
): Promise<Recipe> {
const recipe = new RecipeModel({
...recipeInput,
author: user._id,
} as Recipe);

return await recipe.save();
}

@Mutation(returns => Recipe)
async rate(@Arg("rate") rateInput: RateInput, @Ctx() { user }: Context): Promise<Recipe> {
// find the recipe
const recipe = await RecipeModel.findById(rateInput.recipeId);
if (!recipe) {
throw new Error("Invalid recipe ID");
}

// set the new recipe rate
const newRate: Rate = {
value: rateInput.value,
user: user._id,
date: new Date(),
};

// update the recipe
(recipe.ratings as Rate[]).push(newRate);
await recipe.save();
return recipe;
}

@FieldResolver()
async author(@Root() recipe: Recipe): Promise<User> {
return (await UserModel.findById(recipe.author))!;
}
}
11 changes: 11 additions & 0 deletions examples/typegoose/resolvers/types/rate-input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { ObjectId } from "mongodb";
import { InputType, Field, Int } from "../../../../src";

@InputType()
export class RateInput {
@Field()
recipeId: ObjectId;

@Field(type => Int)
value: number;
}
12 changes: 12 additions & 0 deletions examples/typegoose/resolvers/types/recipe-input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { InputType, Field } from "../../../../src";

import { Recipe } from "../../entities/recipe";

@InputType()
export class RecipeInput implements Partial<Recipe> {
@Field()
title: string;

@Field({ nullable: true })
description?: string;
}
Loading

0 comments on commit c03e70d

Please sign in to comment.