├── sample-env ├── app ├── types.ts ├── resolvers │ ├── types │ │ ├── cart-input.ts │ │ ├── order-input.ts │ │ ├── category-input.ts │ │ ├── user-input.ts │ │ └── product-input.ts │ ├── Categories.ts │ ├── Cart.ts │ ├── User.ts │ ├── Order.ts │ └── Product.ts ├── entities │ ├── Categories.ts │ ├── Cart.ts │ ├── User.ts │ ├── Order.ts │ └── Product.ts └── server.ts ├── .gitignore ├── README.md ├── package.json ├── tsconfig.json └── schema.gql /sample-env: -------------------------------------------------------------------------------- 1 | MONGODB_URI_LOCAL=mongodb://localhost:27017/ 2 | PORT=3333 -------------------------------------------------------------------------------- /app/types.ts: -------------------------------------------------------------------------------- 1 | import { ObjectId } from "mongodb"; 2 | 3 | export type Ref = T | ObjectId; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # node modules 2 | node_modules 3 | 4 | 5 | # API keys and secrets 6 | .env 7 | 8 | # build sources 9 | build 10 | dist -------------------------------------------------------------------------------- /app/resolvers/types/cart-input.ts: -------------------------------------------------------------------------------- 1 | import { InputType, Field, ID } from "type-graphql"; 2 | import { Cart } from "../../entities/Cart"; 3 | 4 | import { ObjectId } from "mongodb"; 5 | 6 | 7 | 8 | 9 | @InputType() 10 | export class CartInput implements Partial { 11 | 12 | @Field(()=> ID) 13 | products: ObjectId; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /app/resolvers/types/order-input.ts: -------------------------------------------------------------------------------- 1 | import { InputType, Field } from "type-graphql"; 2 | import { Order } from "../../entities/Order"; 3 | 4 | 5 | @InputType() 6 | export class OrderInput implements Partial { 7 | 8 | @Field() 9 | user_id: String; 10 | 11 | @Field() 12 | payde: Boolean; 13 | 14 | @Field() 15 | date: Date; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /app/resolvers/types/category-input.ts: -------------------------------------------------------------------------------- 1 | import { InputType, Field } from "type-graphql"; 2 | import { Length } from "class-validator"; 3 | import { Categories } from "../../entities/Categories"; 4 | 5 | 6 | @InputType() 7 | export class CategoriesInput implements Partial { 8 | 9 | @Field() 10 | name: string; 11 | 12 | @Field() 13 | @Length(1, 255) 14 | description: String; 15 | 16 | } 17 | 18 | -------------------------------------------------------------------------------- /app/resolvers/types/user-input.ts: -------------------------------------------------------------------------------- 1 | import { InputType, Field, ID } from "type-graphql"; 2 | import { Length, IsEmail } from "class-validator"; 3 | import { User } from "../../entities/User"; 4 | import { ObjectId } from "mongodb"; 5 | 6 | 7 | 8 | @InputType() 9 | export class UserInput implements Partial { 10 | 11 | @Field() 12 | @Length(1, 255) 13 | username: String; 14 | 15 | @Field() 16 | @IsEmail() 17 | email: String; 18 | 19 | @Field(()=> ID) 20 | cart_id: ObjectId; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /app/entities/Categories.ts: -------------------------------------------------------------------------------- 1 | import { ObjectType, Field, ID } from "type-graphql"; 2 | import { prop as Property, getModelForClass } from "@typegoose/typegoose"; 3 | 4 | 5 | 6 | 7 | @ObjectType({ description: "The Categories model" }) 8 | export class Categories { 9 | @Field(()=> ID) 10 | id: string; 11 | 12 | @Field() 13 | @Property() 14 | name: String; 15 | 16 | @Field() 17 | @Property() 18 | description: String; 19 | } 20 | 21 | 22 | export const CategoriesModel = getModelForClass(Categories); 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/entities/Cart.ts: -------------------------------------------------------------------------------- 1 | import { ObjectType, Field, ID} from "type-graphql"; 2 | import { prop as Property, getModelForClass } from "@typegoose/typegoose"; 3 | 4 | 5 | import { Ref } from "../types"; 6 | 7 | import {Product} from "./Product"; 8 | 9 | 10 | @ObjectType({ description: "The Cart model" }) 11 | export class Cart { 12 | @Field(() => ID) 13 | id: string; 14 | 15 | @Field(_type => String) 16 | @Property({ ref: Product, required: true }) 17 | products: Ref; 18 | _doc: any; 19 | } 20 | 21 | 22 | 23 | export const CartModel = getModelForClass(Cart); -------------------------------------------------------------------------------- /app/resolvers/types/product-input.ts: -------------------------------------------------------------------------------- 1 | import { InputType, Field } from "type-graphql"; 2 | import { Length } from "class-validator"; 3 | import { Product } from "../../entities/Product"; 4 | import { ObjectId } from "mongodb"; 5 | 6 | 7 | 8 | 9 | @InputType() 10 | export class ProductInput implements Partial { 11 | 12 | @Field() 13 | name: String; 14 | 15 | @Field() 16 | @Length(1, 255) 17 | description: String; 18 | 19 | @Field() 20 | color: String; 21 | 22 | @Field() 23 | stock: number; 24 | 25 | @Field() 26 | price: number; 27 | 28 | @Field(()=> String) 29 | category_id: ObjectId; 30 | 31 | } 32 | -------------------------------------------------------------------------------- /app/entities/User.ts: -------------------------------------------------------------------------------- 1 | import { ObjectType, Field, ID } from "type-graphql"; 2 | import { prop as Property, getModelForClass } from "@typegoose/typegoose"; 3 | 4 | import { Ref } from "../types"; 5 | 6 | import {Cart} from "./Cart"; 7 | 8 | 9 | @ObjectType({ description: "The User model" }) 10 | export class User { 11 | [x: string]: any; 12 | @Field(() => ID) 13 | id: number; 14 | 15 | @Field() 16 | @Property({ required: true }) 17 | username: String; 18 | 19 | @Field() 20 | @Property({ required: true }) 21 | email: String; 22 | 23 | @Field(_type => String) 24 | @Property({ ref: Cart, required: true}) 25 | cart_id: Ref 26 | } 27 | 28 | 29 | export const UserModel = getModelForClass(User); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Integrating Typscript with Graphql using Type-Graphl, a modern framework for building Graphql Node JS APIs 2 | 3 | ### To compile the application, run `npm run build-ts` 4 | 5 | ### To start the server, run `npm start` 6 | 7 | ## Dependencies 8 | 9 | - Typgoose, `@typegoose/typegoose` A library for defining Mongoose models using TypeScript classes. 10 | - Type-Graphl, A library for creating GraphQL schema and resolvers with TypeScript, using `classes` and `decorators magic` :)! 11 | - Apollo-server-express, `apollo-server-express`, A library for quickly bootstrapping graphql servers with Apollo and Express 12 | 13 | 14 | ### For a note on other dependencies, please have a look at the `package.json` file. 15 | 16 | 17 | Note: Run `npm install` to install all the projects dependencies... -------------------------------------------------------------------------------- /app/entities/Order.ts: -------------------------------------------------------------------------------- 1 | import { ObjectType, Field, ID } from "type-graphql"; 2 | import { prop as Property, getModelForClass} from "@typegoose/typegoose"; 3 | 4 | 5 | import { Ref } from "../types"; 6 | 7 | import {Product} from "./Product"; 8 | 9 | 10 | @ObjectType({ description: "The Order model" }) 11 | export class Order { 12 | @Field(()=> ID) 13 | id: String; 14 | 15 | @Field() 16 | @Property({ nullable: true }) 17 | user_id: String; 18 | 19 | @Field() 20 | @Property({ required: true }) 21 | payde: Boolean; 22 | 23 | @Field() 24 | @Property({ default: new Date(), required: true, nullable: true }) 25 | date: Date; 26 | 27 | // @Field(_type => Product) 28 | @Property({ ref: Product, required: true }) 29 | products: Ref 30 | _doc: any; 31 | } 32 | 33 | 34 | export const OrderModel = getModelForClass(Order); -------------------------------------------------------------------------------- /app/entities/Product.ts: -------------------------------------------------------------------------------- 1 | import { ObjectType, Field, ID, Int } from "type-graphql"; 2 | import { prop as Property, getModelForClass } from "@typegoose/typegoose"; 3 | import { Ref } from "../types"; 4 | import {Categories} from "./Categories"; 5 | import { __Type } from "graphql"; 6 | 7 | @ObjectType({ description: "The Product model" }) 8 | export class Product { 9 | @Field(() => ID) 10 | id: String; 11 | 12 | @Field() 13 | @Property() 14 | name: String; 15 | 16 | @Field() 17 | @Property() 18 | description: String; 19 | 20 | @Field() 21 | @Property() 22 | color: String; 23 | 24 | @Field(_type => Int) 25 | @Property() 26 | stock: number; 27 | 28 | @Field(_type => Int) 29 | @Property() 30 | price: number; 31 | 32 | @Field(_type => String) 33 | @Property({ref: Categories}) 34 | category_id: Ref; 35 | _doc: any; 36 | 37 | } 38 | 39 | 40 | export const ProductModel = getModelForClass(Product); 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/resolvers/Categories.ts: -------------------------------------------------------------------------------- 1 | import { Resolver, Mutation, Arg, Query } from "type-graphql"; 2 | import { Categories, CategoriesModel } from "../entities/Categories"; 3 | import { CategoriesInput } from "./types/category-input" 4 | 5 | 6 | @Resolver() 7 | export class CategoriesResolver { 8 | 9 | @Query(_returns => Categories, { nullable: false}) 10 | async returnSingleCategory(@Arg("id") id: string){ 11 | return await CategoriesModel.findById({_id:id}); 12 | }; 13 | 14 | @Query(() => [Categories]) 15 | async returnAllCategories(){ 16 | return await CategoriesModel.find(); 17 | }; 18 | 19 | @Mutation(() => Categories) 20 | async createCategory(@Arg("data"){name,description}: CategoriesInput): Promise { 21 | const category = (await CategoriesModel.create({ 22 | name, 23 | description 24 | })).save(); 25 | return category; 26 | }; 27 | 28 | @Mutation(() => Boolean) 29 | async deleteCategory(@Arg("id") id: string) { 30 | await CategoriesModel.deleteOne({id}); 31 | return true; 32 | } 33 | 34 | 35 | 36 | } 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typscript-graphql-logrocket-tutorial", 3 | "version": "1.0.0", 4 | "description": "A typscript and graphql Tutorial", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "npm run serve", 8 | "serve": "node dist/server.js", 9 | "watch-node": "nodemon dist/server.js", 10 | "build-ts": "tsc", 11 | "watch-ts": "tsc -w", 12 | "test": "echo \"Error: no test specified\" && exit 1" 13 | }, 14 | "keywords": [ 15 | "Tyscript", 16 | "Graphql", 17 | "node", 18 | "javascript" 19 | ], 20 | "author": "Alexander Nnakwue", 21 | "license": "MIT", 22 | "devDependencies": { 23 | "@types/express": "^4.17.3", 24 | "@types/graphql": "^14.5.0", 25 | "@types/mongoose": "^5.7.3", 26 | "@types/node": "^13.9.0", 27 | "nodemon": "^2.0.2", 28 | "ts-node": "^8.6.2", 29 | "typescript": "^3.8.3" 30 | }, 31 | "dependencies": { 32 | "@typegoose/typegoose": "^6.4.0", 33 | "apollo-server-express": "^2.11.0", 34 | "class-validator": "^0.11.0", 35 | "express": "^4.17.1", 36 | "mongoose": "^5.9.3", 37 | "graphql": "^14.6.0", 38 | "reflect-metadata": "^0.1.13", 39 | "type-graphql": "^0.17.6" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "esModuleInterop": true, 6 | "allowSyntheticDefaultImports": true, 7 | "target": "es2016", // or newer if your node.js version supports this 8 | "strictNullChecks": true, 9 | "strictFunctionTypes": true, 10 | "noImplicitThis": true, 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": true, 13 | "noImplicitReturns": true, 14 | "skipLibCheck": true, 15 | "declaration": false, 16 | "noFallthroughCasesInSwitch": true, 17 | "composite": false, 18 | "noImplicitAny": true, 19 | "moduleResolution": "node", 20 | "lib": ["dom", "es2016", "esnext.asynciterable"], 21 | "sourceMap": true, 22 | "emitDecoratorMetadata": true, 23 | "strict": false, 24 | "experimentalDecorators": true, 25 | "outDir": "dist", 26 | "rootDir": "app", 27 | "baseUrl": ".", 28 | "paths": { 29 | "*": [ 30 | "node_modules/*", 31 | "app/types/*" 32 | ] 33 | } 34 | }, 35 | "include": [ 36 | "app/**/*", "./app/**/*.ts", "./app/**/*.tsx" 37 | ] 38 | } -------------------------------------------------------------------------------- /app/server.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer } from "apollo-server-express"; 2 | import Express from "express"; 3 | import "reflect-metadata"; 4 | import { buildSchema } from "type-graphql"; 5 | import { connect } from "mongoose"; 6 | 7 | 8 | import {UserResolver} from "./resolvers/User"; 9 | import {ProductResolver} from "./resolvers/Product"; 10 | import {CategoriesResolver} from "./resolvers/Categories"; 11 | import {CartResolver} from "./resolvers/Cart"; 12 | import {OrderResolver} from "./resolvers/Order"; 13 | 14 | 15 | 16 | 17 | 18 | const main = async () => { 19 | 20 | const schema = await buildSchema({ 21 | resolvers: [CategoriesResolver, ProductResolver, UserResolver, CartResolver, OrderResolver ], 22 | emitSchemaFile: true, 23 | validate: false, 24 | }); 25 | 26 | 27 | // create mongoose connection 28 | const mongoose = await connect('mongodb://localhost:27017/test', {useNewUrlParser: true}); 29 | await mongoose.connection; 30 | 31 | 32 | 33 | const server = new ApolloServer({schema}); 34 | 35 | const app = Express(); 36 | 37 | server.applyMiddleware({app}); 38 | 39 | app.listen({ port: 3333 }, () => 40 | console.log(`🚀 Server ready and listening at ==> http://localhost:3333${server.graphqlPath}`)) 41 | 42 | }; 43 | 44 | main().catch((error)=>{ 45 | console.log(error, 'error'); 46 | }) -------------------------------------------------------------------------------- /app/resolvers/Cart.ts: -------------------------------------------------------------------------------- 1 | import { Resolver, Mutation, Arg, Query, FieldResolver, Root } from "type-graphql"; 2 | import { Cart, CartModel } from "../entities/Cart"; 3 | import { CartInput } from "./types/cart-input" 4 | 5 | import { Product, ProductModel } from "../entities/Product"; 6 | 7 | 8 | 9 | @Resolver(_of => Cart) 10 | export class CartResolver { 11 | 12 | @Query(_returns => Cart, { nullable: false}) 13 | async returnSingleCart(@Arg("id") id: string){ 14 | return await CartModel.findById({_id:id}); 15 | }; 16 | 17 | @Query(() => [Cart]) 18 | async returnAllCart(){ 19 | return await CartModel.find(); 20 | }; 21 | 22 | @Mutation(() => Cart) 23 | async createCart(@Arg("data"){products}: CartInput): Promise { 24 | const cart = (await CartModel.create({ 25 | 26 | products 27 | 28 | })).save(); 29 | return cart; 30 | }; 31 | 32 | @Mutation(() => Boolean) 33 | async deleteCart(@Arg("id") id: string) { 34 | await CartModel.deleteOne({id}); 35 | return true; 36 | } 37 | 38 | 39 | @FieldResolver(_type => (Product)) 40 | async product(@Root() cart: Cart): Promise { 41 | console.log(cart, "cart!") 42 | return (await ProductModel.findById(cart._doc.products))!; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /app/resolvers/User.ts: -------------------------------------------------------------------------------- 1 | import { Resolver, Mutation, Arg, Query, FieldResolver, Root } from "type-graphql"; 2 | import { User, UserModel } from "../entities/User"; 3 | import { UserInput } from "./types/user-input" 4 | 5 | import { Cart, CartModel } from "../entities/Cart"; 6 | 7 | 8 | 9 | @Resolver(_of => User) 10 | export class UserResolver { 11 | 12 | @Query(_returns => User, { nullable: false}) 13 | async returnSingleUser(@Arg("id") id: string){ 14 | return await UserModel.findById({_id:id}); 15 | }; 16 | 17 | @Query(() => [User]) 18 | async returnAllUsers(){ 19 | return await UserModel.find(); 20 | }; 21 | 22 | 23 | @Mutation(() => User) 24 | async createUser(@Arg("data"){username,email,cart_id}: UserInput): Promise { 25 | const user = (await UserModel.create({ 26 | username, 27 | email, 28 | cart_id 29 | 30 | })).save(); 31 | return user; 32 | }; 33 | 34 | @Mutation(() => Boolean) 35 | async deleteUser(@Arg("id") id: string) { 36 | await UserModel.deleteOne({id}); 37 | return true; 38 | } 39 | 40 | 41 | @FieldResolver(_type => (Cart)) 42 | async cart(@Root() user: User): Promise { 43 | console.log(user, "userr!") 44 | return (await CartModel.findById(user._doc.cart_id))!; 45 | } 46 | 47 | 48 | } 49 | -------------------------------------------------------------------------------- /app/resolvers/Order.ts: -------------------------------------------------------------------------------- 1 | import { Resolver, Mutation, Arg, Query, FieldResolver, Root } from "type-graphql"; 2 | import { Order, OrderModel } from "../entities/Order"; 3 | import { OrderInput } from "./types/order-input" 4 | 5 | import { Product, ProductModel } from "../entities/Product"; 6 | 7 | 8 | 9 | @Resolver(_of => Order) 10 | export class OrderResolver { 11 | 12 | @Query(_returns => Order, { nullable: false}) 13 | async returnSingleProduct(@Arg("id") id: string){ 14 | return await OrderModel.findById({_id:id}); 15 | }; 16 | 17 | @Query(() => [Order]) 18 | async returnAllOrder(){ 19 | return await OrderModel.find(); 20 | }; 21 | 22 | @Mutation(() => Order) 23 | async createOrder(@Arg("data"){user_id, date, payde}: OrderInput): Promise { 24 | const order = (await OrderModel.create({ 25 | user_id, 26 | date, 27 | payde 28 | 29 | })).save(); 30 | return order; 31 | }; 32 | 33 | @Mutation(() => Boolean) 34 | async deleteOrder(@Arg("id") id: string) { 35 | await OrderModel.deleteOne({id}); 36 | return true; 37 | } 38 | 39 | 40 | @FieldResolver(_type => (Product)) 41 | async products(@Root() order: Order): Promise { 42 | console.log(order, "order!") 43 | return (await ProductModel.findById(order._doc.products))!; 44 | } 45 | 46 | 47 | } 48 | -------------------------------------------------------------------------------- /app/resolvers/Product.ts: -------------------------------------------------------------------------------- 1 | import { Resolver, Mutation, Arg, Query, FieldResolver, Root } from "type-graphql"; 2 | import { Product, ProductModel } from "../entities/Product"; 3 | import { ProductInput } from "./types/product-input" 4 | 5 | import { Categories, CategoriesModel } from "../entities/Categories"; 6 | 7 | 8 | 9 | @Resolver(_of => Product) 10 | export class ProductResolver { 11 | 12 | @Query(_returns => Product, { nullable: false}) 13 | async returnSingleProduct(@Arg("id") id: string){ 14 | return await ProductModel.findById({_id:id}); 15 | }; 16 | 17 | @Query(() => [Product]) 18 | async returnAllProduct(){ 19 | return await ProductModel.find(); 20 | }; 21 | 22 | @Mutation(() => Product) 23 | async createProduct(@Arg("data"){name,description, color, stock, price, category_id}: ProductInput): Promise { 24 | const product = (await ProductModel.create({ 25 | name, 26 | description, 27 | color, 28 | stock, 29 | price, 30 | category_id 31 | 32 | })).save(); 33 | return product; 34 | }; 35 | 36 | @Mutation(() => Boolean) 37 | async deleteProduct(@Arg("id") id: string) { 38 | await ProductModel.deleteOne({id}); 39 | return true; 40 | } 41 | 42 | 43 | @FieldResolver(_type => (Categories)) 44 | async category(@Root() product: Product): Promise { 45 | console.log(product, "product!") 46 | return (await CategoriesModel.findById(product._doc.category_id))!; 47 | } 48 | 49 | 50 | } 51 | -------------------------------------------------------------------------------- /schema.gql: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------- 2 | # !!! THIS FILE WAS GENERATED BY TYPE-GRAPHQL !!! 3 | # !!! DO NOT MODIFY THIS FILE BY YOURSELF !!! 4 | # ----------------------------------------------- 5 | 6 | """The Cart model""" 7 | type Cart { 8 | id: ID! 9 | products: String! 10 | product: Product! 11 | } 12 | 13 | input CartInput { 14 | products: ID! 15 | } 16 | 17 | """The Categories model""" 18 | type Categories { 19 | id: ID! 20 | name: String! 21 | description: String! 22 | } 23 | 24 | input CategoriesInput { 25 | name: String! 26 | description: String! 27 | } 28 | 29 | """ 30 | The javascript `Date` as string. Type represents date and time as the ISO Date string. 31 | """ 32 | scalar DateTime 33 | 34 | type Mutation { 35 | createUser(data: UserInput!): User! 36 | deleteUser(id: String!): Boolean! 37 | createProduct(data: ProductInput!): Product! 38 | deleteProduct(id: String!): Boolean! 39 | createCategory(data: CategoriesInput!): Categories! 40 | deleteCategory(id: String!): Boolean! 41 | createCart(data: CartInput!): Cart! 42 | deleteCart(id: String!): Boolean! 43 | createOrder(data: OrderInput!): Order! 44 | deleteOrder(id: String!): Boolean! 45 | } 46 | 47 | """The Order model""" 48 | type Order { 49 | id: ID! 50 | user_id: String! 51 | payde: Boolean! 52 | date: DateTime! 53 | products: Product! 54 | } 55 | 56 | input OrderInput { 57 | user_id: String! 58 | payde: Boolean! 59 | date: DateTime! 60 | } 61 | 62 | """The Product model""" 63 | type Product { 64 | id: ID! 65 | name: String! 66 | description: String! 67 | color: String! 68 | stock: Int! 69 | price: Int! 70 | category_id: String! 71 | category: Categories! 72 | } 73 | 74 | input ProductInput { 75 | name: String! 76 | description: String! 77 | color: String! 78 | stock: Float! 79 | price: Float! 80 | category_id: String! 81 | } 82 | 83 | type Query { 84 | returnSingleUser(id: String!): User! 85 | returnAllUsers: [User!]! 86 | returnSingleProduct(id: String!): Order! 87 | returnAllProduct: [Product!]! 88 | returnSingleCategory(id: String!): Categories! 89 | returnAllCategories: [Categories!]! 90 | returnSingleCart(id: String!): Cart! 91 | returnAllCart: [Cart!]! 92 | returnAllOrder: [Order!]! 93 | } 94 | 95 | """The User model""" 96 | type User { 97 | id: ID! 98 | username: String! 99 | email: String! 100 | cart_id: String! 101 | cart: Cart! 102 | } 103 | 104 | input UserInput { 105 | username: String! 106 | email: String! 107 | cart_id: ID! 108 | } 109 | --------------------------------------------------------------------------------