├── .gitignore ├── README.md ├── src ├── UserService │ ├── UserResponse.ts │ ├── UserInput.ts │ ├── UserSchema.ts │ ├── UserModel.ts │ └── UserResolver.ts └── server.ts ├── tsconfig.json ├── schema.gql └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .env -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### TypeScript GraphQL Server Starter - Production Ready 2 | 3 | This Repo contains the Source code for article series [Article Series](https://cloudnweb.dev/2020/04/nodejs-graphql-typescript-starter-part-1) 4 | 5 | ``` 6 | npm install 7 | npm run dev 8 | ``` 9 | 10 | -------------------------------------------------------------------------------- /src/UserService/UserResponse.ts: -------------------------------------------------------------------------------- 1 | import { Field, ObjectType } from "type-graphql"; 2 | import UserSchema from "./UserSchema"; 3 | @ObjectType({ description: "User Response" }) 4 | export default class UserResponse { 5 | @Field(() => Boolean) 6 | success: boolean; 7 | 8 | @Field(() => UserSchema) 9 | data: UserSchema | null; 10 | 11 | @Field(() => String) 12 | error: String | null; 13 | } 14 | -------------------------------------------------------------------------------- /src/UserService/UserInput.ts: -------------------------------------------------------------------------------- 1 | import { Field, InputType } from "type-graphql"; 2 | import { IsEmail, Length } from "class-validator"; 3 | 4 | @InputType() 5 | export class UserInput { 6 | @Field() 7 | id: String; 8 | 9 | @Field() 10 | @Length(1, 30) 11 | name: String; 12 | 13 | @Field() 14 | @IsEmail() 15 | @Length(1, 30) 16 | email: String; 17 | 18 | @Field() 19 | password: String; 20 | } 21 | -------------------------------------------------------------------------------- /src/UserService/UserSchema.ts: -------------------------------------------------------------------------------- 1 | import { Field, ObjectType, ID } from "type-graphql"; 2 | import { IsEmail, Length } from "class-validator"; 3 | 4 | @ObjectType({ description: "User Schema" }) 5 | export default class User { 6 | @Field(() => ID) 7 | id: String; 8 | 9 | @Field() 10 | @Length(1, 30) 11 | name: String; 12 | 13 | @Field() 14 | @IsEmail() 15 | @Length(1, 30) 16 | email: String; 17 | 18 | password: String; 19 | } 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2016", 4 | "module": "commonjs", 5 | "lib": ["dom", "es2016", "esnext.asynciterable"], 6 | "moduleResolution": "node", 7 | "outDir": "./dist", 8 | "strict": true, 9 | "strictPropertyInitialization": false, 10 | "sourceMap": true, 11 | "emitDecoratorMetadata": true, 12 | "experimentalDecorators": true 13 | }, 14 | "include": ["./src/**/*", "./src/*"] 15 | } 16 | -------------------------------------------------------------------------------- /schema.gql: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------- 2 | # !!! THIS FILE WAS GENERATED BY TYPE-GRAPHQL !!! 3 | # !!! DO NOT MODIFY THIS FILE BY YOURSELF !!! 4 | # ----------------------------------------------- 5 | 6 | type Mutation { 7 | registerUser(password: String, email: String, name: String): User 8 | } 9 | 10 | type Query { 11 | sample: String 12 | userByID(id: String): User 13 | loginUser(password: String, email: String): UserResponse 14 | } 15 | 16 | """User Schema""" 17 | type User { 18 | id: ID 19 | name: String 20 | email: String 21 | } 22 | 23 | """User Response""" 24 | type UserResponse { 25 | success: Boolean 26 | data: User 27 | error: String 28 | } 29 | -------------------------------------------------------------------------------- /src/UserService/UserModel.ts: -------------------------------------------------------------------------------- 1 | import * as Mongoose from "mongoose"; 2 | 3 | export interface IUser extends Mongoose.Document { 4 | name: String; 5 | email: String; 6 | password: String; 7 | } 8 | 9 | export interface IUserResponse { 10 | success: boolean; 11 | data: string | null; 12 | error: string | null; 13 | } 14 | 15 | const UserSchema: Mongoose.Schema = new Mongoose.Schema( 16 | { 17 | name: { 18 | type: String, 19 | required: true, 20 | }, 21 | email: { 22 | type: String, 23 | required: true, 24 | }, 25 | password: { 26 | type: String, 27 | required: true, 28 | }, 29 | }, 30 | { timestamps: true } 31 | ); 32 | 33 | export default Mongoose.model("User", UserSchema); 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphql-typescript-production-starter", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "ts-node-dev --respawn src/server.ts", 8 | "build": "tsc", 9 | "start": "node ./dist/index.js", 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "@types/bcrypt": "^3.0.0", 17 | "@types/bcryptjs": "^2.4.2", 18 | "@types/express": "^4.17.6", 19 | "@types/mongoose": "^5.7.13", 20 | "@types/node": "^13.13.2", 21 | "dotenv": "^8.2.0", 22 | "nodemon": "^2.0.3", 23 | "ts-node": "^8.9.0", 24 | "ts-node-dev": "^1.0.0-pre.44", 25 | "typescript": "^3.8.3" 26 | }, 27 | "dependencies": { 28 | "apollo-server-express": "^2.12.0", 29 | "bcrypt": "^4.0.1", 30 | "bcryptjs": "^2.4.3", 31 | "class-validator": "^0.12.1", 32 | "express": "^4.17.1", 33 | "graphql": "^14.6.0", 34 | "mongoose": "^5.9.10", 35 | "reflect-metadata": "^0.1.13", 36 | "type-graphql": "^0.17.6" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/server.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer } from "apollo-server-express"; 2 | import * as Express from "express"; 3 | import "reflect-metadata"; 4 | import { buildSchema } from "type-graphql"; 5 | import UserModel from "./UserService/UserModel"; 6 | import { UserResolver } from "./UserService/UserResolver"; 7 | import * as Mongoose from "mongoose"; 8 | 9 | async function startServer() { 10 | require("dotenv").config(__dirname + ".env"); 11 | 12 | const schema = await buildSchema({ 13 | resolvers: [UserResolver], 14 | emitSchemaFile: true, 15 | nullableByDefault: true, 16 | }); 17 | 18 | const app = Express(); 19 | 20 | const MONGO_USER = process.env.MONGODB_USER; 21 | const MONGO_PASS = process.env.MONGODB_PASS; 22 | 23 | Mongoose.connect( 24 | `mongodb+srv://${MONGO_USER}:${MONGO_PASS}@cluster0-xv4mh.mongodb.net/test?retryWrites=true&w=majority`, 25 | { 26 | useNewUrlParser: true, 27 | useUnifiedTopology: true, 28 | } 29 | ) 30 | .then((res) => { 31 | console.log("Mongodb is connected successfully"); 32 | 33 | const server = new ApolloServer({ 34 | schema, 35 | context: () => ({ 36 | userModel: UserModel, 37 | }), 38 | }); 39 | 40 | server.applyMiddleware({ app }); 41 | const PORT = process.env.PORT; 42 | app.listen(PORT, () => { 43 | console.log(`server is running on PORT ${PORT}`); 44 | }); 45 | }) 46 | .catch((err) => { 47 | console.log(err); 48 | }); 49 | } 50 | 51 | startServer(); 52 | -------------------------------------------------------------------------------- /src/UserService/UserResolver.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Arg, 3 | FieldResolver, 4 | Query, 5 | Mutation, 6 | Resolver, 7 | Ctx, 8 | Root, 9 | } from "type-graphql"; 10 | import UserSchema from "./UserSchema"; 11 | import UserResponse from "./UserResponse"; 12 | import { IUser, IUserResponse } from "./UserModel"; 13 | import * as bcrypt from "bcryptjs"; 14 | 15 | @Resolver((of) => UserSchema) 16 | export class UserResolver { 17 | @Query(() => String) 18 | sample(): String { 19 | return "Hello"; 20 | } 21 | 22 | @Query((returns) => UserSchema, { nullable: true }) 23 | async userByID(@Arg("id") id: string, @Ctx() ctx: any): Promise { 24 | const userCollection = await ctx.userModel.findOne({ _id: id }); 25 | 26 | return userCollection; 27 | } 28 | 29 | @Query((returns) => UserResponse) 30 | async loginUser( 31 | @Arg("email") email: string, 32 | @Arg("password") password: string, 33 | @Ctx() ctx: any 34 | ): Promise { 35 | const user = await ctx.userModel.findOne({ 36 | email: email, 37 | }); 38 | 39 | if (user) { 40 | const { err } = await bcrypt.compare(password, user.password); 41 | 42 | if (!!err) { 43 | return { 44 | success: false, 45 | error: "Invalid Credetials", 46 | data: null, 47 | }; 48 | } else { 49 | return { 50 | success: true, 51 | error: null, 52 | data: user, 53 | }; 54 | } 55 | } else { 56 | return { 57 | success: false, 58 | error: "User Not Found", 59 | data: null, 60 | }; 61 | } 62 | } 63 | 64 | @Mutation(() => UserSchema) 65 | async registerUser( 66 | @Arg("name") name: string, 67 | @Arg("email") email: string, 68 | @Arg("password") password: string, 69 | @Ctx() ctx: any 70 | ): Promise { 71 | const hashedPassword = await bcrypt.hash(password, 12); 72 | 73 | const user = await new ctx.userModel({ 74 | name, 75 | email, 76 | password: hashedPassword, 77 | }); 78 | 79 | return user.save(); 80 | } 81 | } 82 | --------------------------------------------------------------------------------