{"id":13683410,"url":"https://github.com/tajpouria/typegraphql-nextjs-boilerplate","last_synced_at":"2025-07-23T17:06:12.681Z","repository":{"id":42159642,"uuid":"211730331","full_name":"tajpouria/typegraphql-nextjs-boilerplate","owner":"tajpouria","description":"Yet Another graphql next template.","archived":false,"fork":false,"pushed_at":"2020-11-21T18:47:16.000Z","size":634,"stargazers_count":44,"open_issues_count":0,"forks_count":7,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-07-19T09:24:42.859Z","etag":null,"topics":["graphql","nextjs","typegraphql","typeorm","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tajpouria.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-09-29T21:46:10.000Z","updated_at":"2024-08-18T13:32:43.000Z","dependencies_parsed_at":"2022-09-10T01:21:45.026Z","dependency_job_id":null,"html_url":"https://github.com/tajpouria/typegraphql-nextjs-boilerplate","commit_stats":null,"previous_names":[],"tags_count":0,"template":true,"template_full_name":null,"purl":"pkg:github/tajpouria/typegraphql-nextjs-boilerplate","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tajpouria%2Ftypegraphql-nextjs-boilerplate","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tajpouria%2Ftypegraphql-nextjs-boilerplate/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tajpouria%2Ftypegraphql-nextjs-boilerplate/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tajpouria%2Ftypegraphql-nextjs-boilerplate/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tajpouria","download_url":"https://codeload.github.com/tajpouria/typegraphql-nextjs-boilerplate/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tajpouria%2Ftypegraphql-nextjs-boilerplate/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266717709,"owners_count":23973384,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-23T02:00:09.312Z","response_time":66,"last_error":null,"robots_txt_status":null,"robots_txt_updated_at":null,"robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["graphql","nextjs","typegraphql","typeorm","typescript"],"created_at":"2024-08-02T13:02:10.331Z","updated_at":"2025-07-23T17:06:12.658Z","avatar_url":"https://github.com/tajpouria.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"# Type GraphQL Series Integrated with NextJS\n\nA quick introduction to [type-graphql](https://github.com/MichalLytek/type-graphql) library with NextJS\n\n## Development bootstrap\n\n```sh\n# Install dependencies\n\u003e npm i\n\n# Setup development infrastructure\n\u003e docker-compose up\n\n# Starting the server\n\u003e npm start\n\n# Starting the web application\n\u003e cd web \u0026\u0026 npm run dev\n```\n\n### bootstrapping\n\n```typescript\nimport { buildSchema, Resolver, Query } from \"type-graphql\";\n\n@Resolver()\nclass HelloResolver {\n    @Query(() =\u003e String, { name: \"hello\", nullable: true })\n    async hello() {\n        return \"hello world\";\n    }\n}\n\n(async () =\u003e {\n    const app = express();\n\n    const schema = await buildSchema({\n        resolver: [__dirname + \"/modules/**/*.ts\"]\n        // resolvers: [HelloResolver]\n    });\n\n    const apolloServer = new ApolloServer({ schema });\n\n    apolloServer.applyMiddleware({ app });\n\n    app.listen(4000, () =\u003e {\n        console.log(\"Listening on port 4000...\");\n    });\n})();\n```\n\n## write mutation\n\n```typescript\n@ObjectType()\n@Entity()\nexport class User extends BaseEntity {\n    @Field(() =\u003e ID)\n    @PrimaryGeneratedColumn()\n    id: number;\n\n    @Field()\n    @Column(\"text\")\n    firstName: string;\n\n    @Field()\n    @Column(\"text\")\n    lastName: string;\n\n    @Field()\n    fullName: string;\n\n    @Field()\n    @Column(\"text\", { unique: true })\n    email: string;\n\n    @Field()\n    @Column(\"text\")\n    password: string;\n\n    @BeforeInsert()\n    async hashPassword() {\n        const hashedPassword = await hash(this.password, 12);\n        this.password = hashedPassword;\n    }\n}\n```\n\n```typescript\n@Resolver(User) // specified as @root\nexport class UserResolver {\n    @Query(() =\u003e String)\n    hello(): string {\n        return \"Hello from user\";\n    }\n\n    @Mutation(() =\u003e User) // it is possible cuz we defined User Entity as an Object type.\n    async register(\n        @Arg(\"firstName\") firstName: string,\n        @Arg(\"lastName\") lastName: string,\n        @Arg(\"email\") email: string,\n        @Arg(\"password\") password: string\n    ): Promise\u003cUser\u003e {\n        const user = await User.create({\n            firstName,\n            lastName,\n            email,\n            password\n        }).save();\n\n        return user;\n    }\n\n    @resolveField(() =\u003e String) // it 's useful to query on fields that not specified as Entity Column\n    fullName(@Root parent: User) {\n        return `${parent.firstName} ${parent.lastName}`;\n    }\n}\n```\n\n_there is also a shorter way to **resolve fields** directly on entity_\n\n./entity/User.ts\n\n```typescript\n@Entity()\nexport class User extends BaseEntity {\n    // other class @Column()\n\n    @Field()\n    fullName(@Root() parent: User): string {\n        return `${parent.firstName} ${parent.lastName}`;\n    }\n}\n```\n\n## Validation\n\n### defining an @inputType and use it as @Arg\n\n\u003e yarn add class-validator\n\n./modules/user/register/RegisterInput.ts\n\n```typescript\nimport { Length, isEmail } from \"class-validation\";\n\n@InputType()\nexport class RegisterInput {\n    @Field()\n    @Length(3, 255)\n    firstName: string;\n\n    @Field()\n    @Length(3, 255)\n    lastName: string;\n\n    @Field()\n    @isEmail()\n    email: string;\n\n    @Field()\n    password: string;\n}\n```\n\n./modules/user/register/userResolver\n\n```typescript\n@Resolver()\nexport class UserResolver {\n    @Mutation(() =\u003e User)\n    register(\n        @Arg(\"input\")\n        { firstName, lastName, email, password }: RegisterInput\n    ): Promise\u003cUser\u003e {\n        // doing register stuff\n    }\n}\n```\n\nhere is how args looks like:\n\n```graphql\nmutation Register {\n    register(input: {firstName, lastName, email, password}){\n        id\n        .\n        .\n        .\n    }\n}\n```\n\n## login and persist session on redis\n\n### store express sessions on redis\n\n\u003e yarn add express-session connect-redis ioredis cors\n\u003e yarn add -D @types/express-session @types/connect-redis @types/ioredis @types/core\n\n```typescript\nimport session from \"express-session\";\nimport connectRedis from \"redis-store\";\nimport Redis from \"ioredis\";\n\nconst RedisStore = connectRedis(session);\n\nconst redis = new Redis();\n\n(() =\u003e {\n    const app = express();\n\n    app.use(cors({ credentials: true, origin: \"http://localhost:4000\" }));\n\n    app.use(\n        session({\n            store: new RedisStore({\n                client: redis as any\n            }),\n            name: \"qid\",\n            secret: \"cat keyboard\",\n            resave: false,\n            saveUninitialized: false,\n            cookie: {\n                httpOnly: true,\n                secure: process.env.NODE_ENV === \"production\",\n                maxAge: 1000 * 60 * 60 * 24 * 7 * 365 // 7 year\n            }\n        })\n    );\n\n    // If application using apollo-server-express applyMiddleware it s also important to setup session middleware _before_ apply app as middleware e.g.\n\n    apolloServer.applyMiddleware({ app });\n})();\n```\n\n### LoginResolver and MeResolver\n\n./modules/user/LoginResolver\n\n```typescript\ninterface MyContext {\n    req: Request;\n}\n\n@Resolver()\nexport class LoginResolver {\n    @Mutation(() =\u003e User, { nullable: true })\n    async login(\n        @Arg(\"input\")\n        { email, password }: LoginInput,\n        @Ctx() ctx: MyContext // accessing context\n    ): Promise\u003cUser | null\u003e {\n        const user = await User.findOne({ where: { email } });\n\n        if (!user) {\n            console.error(\"User not found\");\n            return null;\n        }\n\n        const isValid = await compare(password, user.password);\n\n        if (!isValid) {\n            console.error(\"invalid password\");\n            return null;\n        }\n\n        ctx.req.session!.userId = user.id;\n\n        return user;\n    }\n}\n```\n\n./modules/user/LoginResolver\n\n```typescript\n@Resolver()\nexport class MeResolver {\n    @Query(() =\u003e User, { nullable: true })\n    async me(@Ctx() ctx: MyContext): Promise\u003cUser | undefined\u003e {\n        const userId = ctx.req.session!.userId;\n        if (!userId) {\n            return undefined;\n        }\n\n        const user = await User.findOne(userId);\n\n        return user;\n    }\n}\n```\n\n## Middleware\n\n### @Authorized()\n\n```typescript\nimport {@Authorized } from 'type-graphql'\n\n@Resolver()\nexport class ProtectedHelloResolver {\n    @Query(() =\u003e String, { nullable: true })\n    @Authorized()\n    hello() {\n        return \"hello\";\n    }\n}\n(async() =\u003e {\n\n    const schema = await buildSchema({\n        resolvers: [UserResolver, LoginResolver, MeResolver],\n        authChecker({ context }: { context: MyContext }) {\n            if (!context.req.session!.userId) {\n                return false;\n            }\n            return true;\n        }\n    });\n})()\n```\n\n### Custom Middleware\n\n./modules/middleware/IsAuth.ts\n\n```typescript\nimport { MiddlewareFn } from \"type-graphql\";\nimport { MyContext } from \"../../types/MyContext\";\n\nexport const IsAuth: MiddlewareFn\u003cMyContext\u003e = async ({ context }, next) =\u003e {\n    if (!context.req.session!.userId) {\n        throw new Error(\"Not Authorized!\");\n    }\n    return next();\n};\n```\n\n./modules/userResolver.ts\n\n```typescript\n@Resolver()\nexport class UserResolver {\n    @Query(() =\u003e String)\n    @UseMiddleware(IsAuth)\n    hello(): string {\n        return \"hello user\";\n    }\n\n    /* \n    .\n    .\n    .\n    */\n}\n```\n\n## Confirm User using Confirmation Email with nodemailer\n\n### confirmation @Column on @Entity and forbid not confirmed user to login\n\n./entity/User.ts\n\n```typescript\n@ObjectType()\n@Entity()\nexport class User extends BaseEntity {\n    /* \n    .\n    .\n    .\n    */\n    @Field()\n    @Column(\"bool\", { default: false })\n    confirmed: boolean;\n}\n```\n\n./modules/user/loginResolver.ts\n\n```typescript\n@Resolver()\nexport class LoginResolver {\n    @Mutation(() =\u003e User, { nullable: true })\n    async login(\n        @Arg(\"input\")\n        { email, password }: LoginInput,\n        @Ctx() ctx: MyContext\n    ): Promise\u003cUser | null\u003e {\n        /*\n        .\n        .\n        .\n        */\n        if (!user.confirmed) {\n            console.error(\"User not confirmed\");\n            return null;\n        }\n        /*\n        .\n        .\n        .\n        */\n    }\n}\n```\n\n### install in setting up nodemailer sendingEmail function\n\n\u003e yarn add nodemailer\n\u003e yarn add -D nodemailer\n\n./modules/utils/sendConfirmationEmail.ts\n\n```typescript\nimport nodemailer from \"nodemailer\";\n\nexport async function sendConfirmationEmail(\n    email: string,\n    confirmationLink: string\n) {\n    const testAccount = await nodemailer.createTestAccount();\n\n    let transporter = nodemailer.createTransport({\n        host: \"smtp.ethereal.email\",\n        port: 587,\n        secure: false, // true for 465, false for other ports\n        auth: {\n            user: testAccount.user, // generated ethereal user\n            pass: testAccount.pass // generated ethereal password\n        }\n    });\n\n    const info = await transporter.sendMail({\n        from: '\"Fred Foo 👻\" \u003cfoo@example.com\u003e', // sender address\n        to: \"bar@example.com, baz@example.com\", // list of receivers\n        subject: \"Confirmation ✔\", // Subject line\n        text: \"Hello world?\", // plain text body\n        html: `\u003ca href=\"${confirmationLink}\"\u003e${confirmationLink}\u003c/a\u003e` // html body\n    });\n\n    console.log(\"Message sent: %s\", info.messageId);\n    console.log(\"Preview URL: %s\", nodemailer.getTestMessageUrl(info));\n}\n```\n\n### modify register to set token and send confirmation email\n\n./modules/user/userResolver.ts\n\n```typescript\nimport { v4 } from \"uuid\";\nimport { redis } from \"../../redis\";\nimport { sendConfirmationEmail } from \"../utils/sendConfirmationEmail\";\n\nconst createAndSetConfirmationLink = async (userId: number) =\u003e {\n    const token = v4();\n\n    await redis.set(token, userId, \"ex\", 60 * 60 * 24); // *** expiration in 1 day\n\n    return `http://localhost:3000/${token}`;\n};\n\n@Resolver()\nexport class UserResolver {\n    @Mutation(() =\u003e User)\n    async register(\n        @Arg(\"input\")\n        { firstName, lastName, email, password }: RegisterInput\n    ): Promise\u003cUser\u003e {\n        const user = await User.create({\n            firstName,\n            lastName,\n            email,\n            password\n        }).save();\n\n        const confirmationLink = await createAndSetConfirmationLink(user.id);\n        await sendConfirmationEmail(email, confirmationLink);\n\n        return user;\n    }\n}\n```\n\n### confirmUserResolver\n\n./modules/user/confirmUserResolver.ts\n\n```typescript\n@Resolver()\nexport class ConfirmUserResolver {\n    @Mutation(() =\u003e Boolean)\n    async confirm(@Arg(\"token\") token: string): Promise\u003cboolean\u003e {\n        const userId = await redis.get(token);\n\n        if (!userId) {\n            return false;\n        }\n\n        await redis.del(token); // *** delete stored token\n        await User.update(userId, { confirmed: true });\n\n        return true;\n    }\n}\n```\n\n## logout\n\n./modules/LogoutResolver.ts\n\n```typescript\n@Resolver()\nexport class LogoutResolver {\n    @Mutation(() =\u003e Boolean)\n    async logout(@Ctx() ctx: MyContext): Promise\u003cboolean\u003e {\n        return new Promise((resolve, reject) =\u003e {\n            // destroy the session\n            ctx.req.session!.destroy((err) =\u003e {\n                if (err) {\n                    console.error(err);\n                    reject(false);\n                }\n\n                ctx.res.clearCookie(\"qid\"); // clear the cookie\n                resolve(true);\n            });\n        });\n    }\n}\n```\n\n### inputMixin\n\n./modules/shared/PasswordMixin.ts\n\n```typescript\nimport { ClassType, InputType, Field } from \"type-graphql\";\nimport { MinLength } from \"class-validator\";\n\nexport const PasswordMixin = \u003cT extends ClassType\u003e(BaseClass: T) =\u003e {\n    @InputType({ isAbstract: true }) // Error: Schema must contain unique named types but contains multiple types named\n    class PasswordInput extends BaseClass {\n        @Field()\n        @MinLength(3)\n        password: string;\n    }\n\n    return PasswordInput;\n};\n```\n\n./modules/login/LoginInput.ts\n\n```typescript\nimport { PasswordMixin } from \"../shared/PasswordMixin\";\n\n@InputType()\nexport class LoginInput extends PasswordMixin(class {}) {\n    // Instead of class {} you can place whenever input class you want (basic nesting extension)\n    @Field()\n    @IsEmail()\n    email: string;\n}\n```\n\n:4000/graphql\n\n```graphql\ntype LoginInput {\n    password: String! # added by extends\n    email: String!\n}\n```\n\n## Testing Resolvers\n\n### setup test environment\n\n-   install dependencies\n    \u003e yarn add --dev jest typescript ts-jest @types/jest\n-   generate jest.config.jes\n    \u003e yarn ts-jest config:init\n\n./ jest.config.js\n\n```javascript\nmodule.exports = {\n    preset: \"ts-jest\",\n    testEnvironment: \"node\",\n    forceExit: true,\n    verbose: true,\n    setupFilesAfterEnv: [\"./jest.setup.js\"] // this file added manually because integration test take a while to resolve\n};\n```\n\n./jest.setup.js\n\n```javascript\njest.setTimeout(30000);\n```\n\n-   adding create connection script\n\nsrc/test-utils/testConn.ts\n\n```typescript\nexport const testConn = (drop: boolean = false) =\u003e {\n    createConnection({\n{\n        type: \"postgres\",\n        host: \"localhost\",\n        port: 5432,\n        username: \"postgres\",\n        password: \"postgres\",\n        database: \"typegraphql_series_test\",\n        synchronize: drop,\n        dropSchema: drop, // drop drop database tables then connect\n        entities: [__dirname + \"/../entity/*.*\"] // path to entities\n    })\n}\n```\n\nsrc/test-utils/setup.ts\n\n```typescript\ntestConn(true).then(() =\u003e process.exit());\n```\n\n./package.json\n\n```json\n{\n    \"script\": {\n        \"setup:db\": \"yarn ts-node src/test-utils/setup.ts\",\n        \"test\": \"yarn run setup:db \u0026\u0026 jest --detectOpenHandles\"\n    }\n}\n```\n\n### writing tests\n\nfollowing contains two integration tests, using direct call graphql schema\n\n./test-utils/gCall.ts\n\n```typescript\nimport { graphql, GraphQLSchema } from \"graphql\";\nimport Maybe from \"graphql/tsutils/Maybe\";\n\nimport { createSchema } from \"../createSchema\";\n\ninterface Options {\n    source: string;\n    variableValues?: Maybe\u003c{\n        [key: string]: any;\n    }\u003e;\n    userId?: number;\n}\n\nlet schema: GraphQLSchema;\n\nexport const gCall = async ({ source, variableValues, userId }: Options) =\u003e {\n    if (!schema) {\n        schema = await createSchema(); // type-graphql await buildSchema({ resolvers })\n    }\n\n    return graphql({\n        schema,\n        source, // Query or Mutation : string\n        variableValues,\n        contextValue: {\n            // query context\n            req: {\n                session: { userId }\n            },\n            res: {\n                clearCookie: jest.fn()\n            }\n        }\n    });\n};\n```\n\nsrc/\\_\\_tests\\_\\_/register.spec.ts\n\n```typescript\nimport { Connection } from \"typeorm\";\nimport faker from \"faker\";\nimport { testConn } from \"../test-utils/testConn\";\nimport { gCall } from \"../test-utils/gCall\";\nimport { User } from \"../entity/User\";\n\ndescribe(\"RegisterResolver\", () =\u003e {\n    let connection: Connection;\n    beforeAll(async () =\u003e {\n        connection = await testConn();\n    });\n\n    afterAll(async () =\u003e {\n        await connection.close();\n    });\n\n    it(\"register the valid user\", async () =\u003e {\n        const registerMutation = `mutation Register($input: RegisterInput!) {\n          register(input: $input) {\n            id\n            firstName\n            lastName\n            fullName\n            email\n            password\n            confirmed\n          }\n        }\n        `;\n\n        const person = {\n            firstName: faker.name.firstName(),\n            lastName: faker.name.lastName(),\n            email: faker.internet.email(),\n            password: faker.internet.password()\n        };\n\n        const response = await gCall({\n            source: registerMutation,\n            variableValues: {\n                input: person\n            }\n        });\n\n        expect(response).toMatchObject({\n            data: {\n                register: {\n                    firstName: person.firstName,\n                    lastName: person.lastName,\n                    email: person.email,\n                    confirmed: false\n                }\n            }\n        });\n\n        const user = await User.findOne({ where: { email: person.email } });\n        expect(user).toBeDefined();\n    });\n});\n```\n\nsrc/\\_\\_tests\\_\\_/me.spec.ts\n\n```typescript\nimport { Connection } from \"typeorm\";\nimport faker from \"faker\";\nimport { testConn } from \"../test-utils/testConn\";\nimport { gCall } from \"../test-utils/gCall\";\nimport { User } from \"../entity/User\";\n\ndescribe(\"MeResolver\", () =\u003e {\n    let connection: Connection;\n    beforeAll(async () =\u003e {\n        connection = await testConn();\n    });\n\n    afterAll(async () =\u003e {\n        await connection.close();\n    });\n\n    it(\"returns the user if userId is available in request session\", async () =\u003e {\n        const meQuery = `query Me {\n   me {\n     id\n     firstName\n     lastName\n     fullName\n     email\n     password\n     confirmed\n   }\n }`;\n\n        const user = await User.create({\n            firstName: faker.name.firstName(),\n            lastName: faker.name.lastName(),\n            email: faker.internet.email(),\n            password: faker.internet.password()\n        }).save();\n\n        const response = await gCall({ source: meQuery, userId: user.id });\n\n        expect(response).toMatchObject({\n            data: {\n                me: {\n                    id: user.id.toString(),\n                    firstName: user.firstName,\n                    lastName: user.lastName,\n                    email: user.email\n                }\n            }\n        });\n    });\n\n    it(\"returns null if userId is NOT available in request session\", async () =\u003e {\n        const meQuery = `query Me {\n   me {\n     id\n     firstName\n     lastName\n     fullName\n     email\n     password\n     confirmed\n   }\n }`;\n\n        const response = await gCall({ source: meQuery });\n\n        expect(response).toMatchObject({\n            data: {\n                me: null\n            }\n        });\n    });\n});\n```\n\n## Higher Order Resolvers (resolver factory)\n\nsrc/modules/GenericResolver/CreateResover\n\n```typescript\nimport {\n    ClassType,\n    Resolver,\n    Mutation,\n    Arg,\n    UseMiddleware\n} from \"type-graphql\";\nimport { Middleware } from \"type-graphql/dist/interfaces/Middleware\";\n\nexport const createCreateResolver = \u003cT extends ClassType, K extends ClassType\u003e(\n    suffix: string,\n    ReturnType: T,\n    InputType: K,\n    Entity: any,\n    middleware?: Middleware\u003cany\u003e[]\n) =\u003e {\n    @Resolver()\n    class BaseResolver {\n        @Mutation(() =\u003e ReturnType, { name: `create${suffix}` })\n        @UseMiddleware(...(middleware || []))\n        async create(@Arg(\"input\", () =\u003e InputType) input: any) {\n            return await Entity.create(input).save();\n        }\n    }\n    return BaseResolver;\n};\n```\n\nsrc/modules/user/createUserResolver\n\n```typescript\nimport { createCreateResolver } from \"../GenericResolver/CreateResolver\";\nimport { User } from \"../../entity/User\";\nimport { RegisterInput } from \"../user/register/RegisterInput\";\n\nexport const CreateUser = createCreateResolver(\n    \"User\",\n    User,\n    RegisterInput,\n    User\n);\n```\n\n## Query Complexity\n\n**Query complexity** is a tactic that can be addded to a graphql server to prevent abuse.\nspecifically prevent user from sending too much query that make server crash or slow down.\n\n\u003e yarn add graphql-query-complexity\n\nsrc/index.ts\n\n```typescript\nimport {\n    getComplexity,\n    fieldConfigEstimator,\n    simpleEstimator\n} from \"graphql-query-complexity\";\nimport { separateOperations } from \"graphql\";\n\nconst apolloServer = new ApolloServer({\n    schema,\n    context: ({ req, res }) =\u003e ({ req, res }),\n    plugins: [\n        {\n            requestDidStart: () =\u003e ({\n                didResolveOperation({ request, document }) {\n                    /**\n                     * This provides GraphQL query analysis to be able to react on complex queries to your GraphQL server.\n                     * This can be used to protect your GraphQL servers against resource exhaustion and DoS attacks.\n                     * More documentation can be found at https://github.com/ivome/graphql-query-complexity.\n                     */\n                    const complexity = getComplexity({\n                        // Our built schema\n                        schema,\n                        // To calculate query complexity properly,\n                        // we have to check if the document contains multiple operations\n                        // and eventually extract it operation from the whole query document.\n                        query: request.operationName\n                            ? separateOperations(document)[\n                                  request.operationName\n                              ]\n                            : document,\n                        // The variables for our GraphQL query\n                        variables: request.variables,\n                        // Add any number of estimators. The estimators are invoked in order, the first\n                        // numeric value that is being returned by an estimator is used as the field complexity.\n                        // If no estimator returns a value, an exception is raised.\n                        estimators: [\n                            // Using fieldConfigEstimator is mandatory to make it work with type-graphql.\n                            fieldConfigEstimator(),\n                            // Add more estimators here...\n                            // This will assign each field a complexity of 1\n                            // if no other estimator returned a value.\n                            simpleEstimator({ defaultComplexity: 1 })\n                        ]\n                    });\n                    // Here we can react to the calculated complexity,\n                    // like compare it with max and throw error when the threshold is reached.\n                    if (complexity \u003e= 10) {\n                        throw new Error(\n                            `Sorry, too complicated query! ${complexity} is over 10 that is the max allowed complexity.`\n                        );\n                    }\n                    // And here we can e.g. subtract the complexity point from hourly API calls limit.\n                    console.log(\"Used query complexity points:\", complexity);\n                }\n            })\n        }\n    ]\n});\n```\n\nsrc/modules/usersResolvers.ts\n\n```typescript\n@Resolver()\nexport class UsersResolver{\n    @Query(() =\u003e User, {complexity : 4}) // this complexity option will added to server complexity then try to resolve\n    /*\n    .\n    .\n    .\n     */\n}\n```\n\ne.g.\n\n```graphql\n# Query\nquery {\n  users {\n    id\n    firstName\n    lastName\n    fullName\n    email\n    password\n    confirmed\n  }\n}\n\n\n# Result\n{\n  \"error\": {\n    \"errors\": [\n      {\n        \"message\": \"Sorry, too complicated query! 12 is over 10 that is the max allowed complexity.\"\n      }\n    ]\n  }\n}\n```\n\n\u003chr/\u003e\n\n### setup next_with_typescript_example\n\n\u003e npx create-next-app --example with-typescript web\n\u003e yarn yarn upgrade --interative\n\n### setup next_with_apollo_client_auth\n\nhttps://github.com/zeit/next.js/blob/canary/examples/with-apollo-auth/lib/apollo.js\n\n## Introduction to [ Formik ](https://jaredpalmer.com/formik/)\n\n\u003e yarn add formik\n\n_yup for validation_\n\n\u003e yarn add yup\n\n### withFormik basics\n\n```jsx\nimport { withFormik } from \"formik\";\n\nconst myForm = ({email, handleChange, onSubmit}) =\u003e {\n    return(\n      \u003cform onSubmit={handleSubmit}\u003e\n            \u003cinput\n                name=\"email\"\n                placeholder=\"email\"\n                onChange={handleChange}\n                value={email}\n            /\u003e\n      \u003c/form \u003e\n    )\n}\n\nexport default withFormik({\n    mapPropsToValues() {\n        return {\n            email: \"\",\n            password: \"\"\n        },\n   onSubmit(values){\n       console.log(values)\n   }\n    }\n})(MyForm);\n```\n\n### using Form and Field\n\n```jsx\nimport { Form, withFormik, Field } from \"formik\";\n\nconst FormikIntro = ({ values }) =\u003e {\n    return (\n        \u003cForm\u003e\n            \u003cField name=\"email\" type=\"email\" placeholder=\"email\" /\u003e\n            \u003cField name=\"password\" type=\"password\" placeholder=\"placeholder=\" /\u003e\n            \u003clabel htmlFor=\"checkBox\"\u003e\n                \u003cField name=\"rules\" type=\"checkBox\" checked={values.rules} /\u003eI\n                agree all rules.\n            \u003c/label\u003e\n            \u003cField name=\"plan\" component=\"select\"\u003e\n                \u003coption value=\"free\"\u003eFree\u003c/option\u003e\n                \u003coption value=\"premium\"\u003ePremium\u003c/option\u003e\n            \u003c/Field\u003e\n            \u003cbutton type=\"submit\"\u003eSubmit\u003c/button\u003e\n        \u003c/Form\u003e\n    );\n};\n\nexport default withFormik({\n    mapPropsToValues() {\n        return { email: \"\", password: \"\", rules: true, plan: \"free\" };\n    },\n    handleSubmit(values) {\n        console.table(values);\n    }\n})(FormikIntro);\n```\n\n### validation using [ Yup ](https://github.com/jquense/yup) and errorHandling\n\n```jsx\nimport { Form, withFormik, Field } from \"formik\";\nimport { object, string } from \"yup\";\n\nconst FormikIntro = ({ values, touched, errors, isSubmitting }) =\u003e {\n    return (\n        \u003cForm\u003e\n            {touched.email \u0026\u0026 errors.email \u0026\u0026 \u003cp\u003e{errors.email}\u003c/p\u003e}\n            \u003cField name=\"email\" type=\"email\" placeholder=\"email\" /\u003e\n            {touched.password \u0026\u0026 errors.password \u0026\u0026 \u003cp\u003e{errors.password}\u003c/p\u003e}\n            \u003cField name=\"password\" type=\"password\" placeholder=\"placeholder=\" /\u003e\n            \u003clabel htmlFor=\"checkBox\"\u003e\n                \u003cField name=\"rules\" type=\"checkBox\" checked={values.rules} /\u003eI\n                agree all rules.\n            \u003c/label\u003e\n            \u003cField name=\"plan\" component=\"select\"\u003e\n                \u003coption value=\"free\"\u003eFree\u003c/option\u003e\n                \u003coption value=\"premium\"\u003ePremium\u003c/option\u003e\n            \u003c/Field\u003e\n            \u003cbutton disabled={isSubmitting} type=\"submit\"\u003e\n                Submit\n            \u003c/button\u003e\n        \u003c/Form\u003e\n    );\n};\n\nexport default withFormik({\n    mapPropsToValues() {\n        return { email: \"\", password: \"\", rules: true, plan: \"free\" };\n    },\n    handleSubmit(values, { setError, resetForm, setSubmitting }) {\n        console.table(values);\n        setTimeout(() =\u003e {\n            if (email === \"givenEmail@gmail.com\") {\n                setError({ email: \"Email is already given\" });\n            }\n            resetForm();\n            setSubmitting(false);\n        }, 2000);\n    },\n    validationSchema: object().shape({\n        email: string().email(\"Email not valid\").required(\"Email is required\"),\n        password: string()\n            .min(9, \"Password must be 9 character or longer\")\n            .required(\"Password is required\")\n    })\n})(FormikIntro);\n```\n\n### passing props methods\n\n./components/fields/InputField.tsx\n\n```typescript\nimport { FieldProps } from \"formik\";\n\ntype InputProps = React.DetailedHTMLProps\u003c\n    React.InputHTMLAttributes\u003cHTMLInputElement\u003e,\n    HTMLInputElement\n\u003e;\n\nexport const InputField: React.FC\u003cFormikProps \u0026 InputProps\u003e = ({\n    form,\n    field,\n    props\n}) =\u003e {\n    const errorMessage = touched[field.name] \u0026\u0026 errors[field.name];\n\n    return \u003cinput {...fields} {...props} /\u003e;\n};\n```\n\n./pages/register.tsx\n\n```typescript\nimport * as React from \"react\";\nimport { Formik, Form, Field } from \"formik\";\nimport Layout from \"../components/Layout\";\nimport { useRegisterMutation } from \"../generated/graphql\";\nimport { withApollo } from \"../lib/apollo\";\n\nconst RegisterPage: React.FC = () =\u003e {\n    const [register] = useRegisterMutation();\n    const handleSubmit = React.useCallback(async values =\u003e {\n        const response = await register({ variables: { input: values } });\n        console.log(response);\n    }, []);\n\n    return (\n        \u003cFormik\n            initialValues={{\n                firstName: \"\",\n                lastName: \"\",\n                email: \"\",\n                password: \"\"\n            }}\n            onSubmit={handleSubmit}\n        \u003e\n            {() =\u003e (\n                {({ isSubmitting }) =\u003e (\n                \u003cLayout title=\"Register\"\u003e\n                    \u003cForm\u003e\n                        \u003cdiv\u003e\n                            \u003cField\n                                name=\"firstName\"\n                                placeholder=\"firstName\"\n                                component={InputField}\n                            /\u003e\n                        \u003c/div\u003e\n                        \u003cdiv\u003e\n                            \u003cField\n                                name=\"lastName\"\n                                placeholder=\"lastName\"\n                                component={InputField}\n                            /\u003e\n                        \u003c/div\u003e\n                        \u003cdiv\u003e\n                            \u003cField\n                                name=\"email\"\n                                type=\"email\"\n                                placeholder=\"email\"\n                                component={InputField}\n                            /\u003e\n                        \u003c/div\u003e\n                        \u003cdiv\u003e\n                            \u003cField\n                                name=\"password\"\n                                type=\"password\"\n                                placeholder=\"password\"\n                                component={InputField}\n                            /\u003e\n                        \u003c/div\u003e\n                        \u003cbutton disabled={isSubmitting} type=\"submit\"\u003e\n                            Submit\n                        \u003c/button\u003e\n                    \u003c/Form\u003e\n                \u003c/Layout\u003e\n            )}\n            )}\n        \u003c/Formik\u003e\n    );\n};\n\nexport default withApollo(RegisterPage);\n```\n\n## Handling ApolloClient errors with Formik\n\n```typescript\nconst handleSubmit = React.useCallback(\n    async (values, { setErrors, setSubmitting }) =\u003e {\n        setSubmitting(true);\n        try {\n            await register({ variables: { input: values } });\n            setSubmitting(false);\n        } catch (err) {\n            const errors: { [key: string]: string } = {};\n            err.graphQLErrors[0].extensions.exception.validationErrors.forEach(\n                ({\n                    property,\n                    constraints\n                }: {\n                    property: string;\n                    constraints: { [key: string]: string };\n                }) =\u003e {\n                    errors[property] = Object.values(constraints)[0];\n                }\n            );\n\n            setErrors(errors);\n            setSubmitting(false);\n        }\n    },\n    []\n);\n```\n\n## Handling Protected Routes in Next\n\n### redirect on server side rendering\n\n./lib/apollo.tsx\n\n```typescript\ntry {\n    // Run all GraphQL queries\n    const { getDataFromTree } = await import(\"@apollo/react-ssr\");\n    await getDataFromTree(\n        \u003cAppTree\n            pagProps={{\n                ...pageProps,\n                apolloClient\n            }}\n        /\u003e\n    );\n} catch (error) {\n    console.error(\"Error while running `getDataFromTree`\", error);\n\n    // *** handling server side rendering auth routes\n    if (error.graphQLErrors[0].message.includes(\"not authenticated\")) {\n        /*\n        actually we send on server side we throw and Error with not authenticated message then we can redirect user base upon it:\n        ./modules/middleware/IsAuth.ts\n        if(req.session.userId){\n            throw new Error('not authenticated')\n        }\n        */\n        redirect(ctx, \"/login\");\n    }\n}\n```\n\n### Router.replace in on the client\n\n\u003e yarn add apollo-link-error\n\n./lib/apollo.tsx\n\n```typescript\nimport { onError } from \"apollo-link-error\";\n\nconst ErrorLink = onError(({ graphQLErrors, networkError }) =\u003e {\n    if (graphQLErrors)\n        graphQLErrors.map(({ message, locations, path }) =\u003e {\n            console.log(\n                `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`\n            );\n            /*\n        actually we send on server side we throw and Error with not authenticated message then we can redirect user base upon it:\n        ./modules/middleware/IsAuth.ts\n        if(req.session.userId){\n            throw new Error('not authenticated')\n        }\n        */\n            if (\n                message.includes(\"not authenticated\") \u0026\u0026\n                typeof window !== \"undefined\"\n            ) {\n                Router.replace(\"/login\"); // *** or Router.push('/login')\n            }\n        });\n    if (networkError) console.log(`[Network error]: ${networkError}`);\n});\n\nreturn new ApolloClient({\n    ssrMode: typeof window === \"undefined\",\n    link: ErrorLink.concat(authLink.concat(httpLink)), // *** set multipleLink using ApolloLink1.concat(ApolloLink2.concat(httpLink))\n    cache: new InMemoryCache().restore(initialState)\n});\n```\n\n## sundry\n\n## ts-node-dev\n\nTweaked version of node-dev that uses ts-node under the hood.\n\n\u003e yarn add -D ts-node-dev\n\n```json\n{\n    \"start\": \"tsnd --respawn  src/index.ts\"\n}\n```\n\n## [ GraphQL SDL review ](https://alligator.io/graphql/graphql-sdl/)\n\n### the basics\n\n```graphql\n# Enumeration type\nenum Priority {\n    LOW\n    MEDIUM\n    HIGH\n}\n\ntype Todo {\n    id: ID!\n    name: String!\n    description: String!\n    priority: String!\n}\n\ntype Query {\n    todo(id: ID!): Todo\n    allTodos: [Todo!]!\n}\n\ntype Mutation {\n    addTodo(name: String!, priority: Priority = LOW): Todo!\n    removeTodo(id: ID!): Todo!\n}\n\nschema {\n    query: Query\n    mutation: Mutation\n}\n```\n\n#### Object Types\n\n-   are defined with the type keyword and start with a capital letter by convention.\n-   Each field in an object type can be resolve to either other object types or scalar types.\n-   Only the Query root type is required in all GraphQL schemas\n-   a Subscription root type is also available, to define operations that a client can subscribe to\n\n#### Built-In Scalar Types\n\nThere are 5 built-in scalar types with GraphQL: Int, Float, String, Boolean and ID (The ID type resolves to a string, but expects a unique value).\n\n#### Enumeration Types\n\nEnumeration types allow to define a specific subset of possible values for a type.\n\n#### Type Modifiers\n\nmodifiers can be used on the type that a field resolves to by using characters like ! and \\[…\\]\n\n-   String : nullable string (the resolved value can be null)\n-   String! : Non-nullable string (if the resolved value is null, an error will be raised)\n-   \\[String\\] : Nullable list of nullable string values. The entire value can be null, or specific list elements can be null\n-   \\[String!\\] : Nullable list of non-nullable string values. Then entire value can be null, but specific list elements cannot be null\n-   \\[String!\\]! : Non-nullable list of non-nullable string values\n\n#### Comments\n\nComments are added with the # symbol and only single-line comments are allowed.\n\n#### Custom Scalar Types\n\nIt’s also possible to define custom scalar types with a syntax like this:\n\n```graphql\nscalar DateTime\n```\n\n#### Union Types\n\nUnion types define a type that can resolve to a number of possible **object types**:\n\n```graphql\n# ...\n\nunion Vehicle = Car | Boat | Plane\n\ntype Query {\n    getVehicle(id: ID!): Vehicle!\n}\n```\n\nWith union types, on the client, inline fragments have to be used to select the desired fields depending on what subtype is being resolved\n\n```graphql\nquery {\n    getVehicle {\n        ... on Car {\n            yead\n        }\n        ... on Boat {\n            color\n        }\n        ... on Plane {\n            seating\n        }\n    }\n}\n```\n\n#### Interfaces\n\nInterfaces are somewhat similar to union types, but they allow multiple object types to share some fields:\n\n```graphql\ninterface Vehicle {\n    color: String\n    make: String\n    speed: Int\n}\n\ntype Car implements Vehicle {\n    color: String\n    make: String\n    speed: Int\n    model: String\n}\n```\n\nEach type that implements an interface need to have fields corresponding to all the interface’s fields, but can also have aditional fields of their own.\n\nThis way, on the client, inline fragments can be used to get fields that are unique to certain types:\n\n```graphql\ngraphql {\n    getVehicle {\n        color\n        make\n        ...on Car {\n            model\n        }\n    }\n}\n\n```\n\n#### Input Types\n\nWhen a query or mutation expects multiple arguments, it can be easier to define input types where each field represents an argument:\n\n```graphql\n#...\n\ninput NewTodoInput {\n    name: String!\n    priority: Priority\n}\n\ntype Mutation {\n    addTodo(newTodoInput: NewTodoInput!): Todo!\n}\n```\n\n#### Schema Documentation\n\nThere’s also a syntax to add human-readable documentation for types and fields, which can become really helpful when using a tool like GraphiQL or GraphQL Playground to browse the documentation for a schema.\n\n```graphql\n\"\"\"\nPriority level\n\"\"\"\nenum Priority {\n    LOW\n    MEDIUM\n    HIGH\n}\n\ntype Todo {\n    id: ID!\n    name: String!\n    \"\"\"\n    Useful description for todo item\n    \"\"\"\n    description: String\n    priority: Priority!\n}\n\n\"\"\"\nQueries available on todo app service\n\"\"\"\ntype Query {\n    \"\"\"\n    Get one todo item\n    \"\"\"\n    todo(id: ID!): Todo\n\n    \"\"\"\n    List of all todo items\n    \"\"\"\n    allTodos: [Todo!]!\n}\n\ntype Mutation {\n    addTodo(\n        \"Name for todo item\"\n        name: String!\n        \"Priority levl of todo item\"\n        priority: Priority = LOW\n    ): Todo\n\n    removeTodo(id: ID!): Todo!\n}\n\nschema {\n    query: Query\n    mutation: Mutation\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftajpouria%2Ftypegraphql-nextjs-boilerplate","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftajpouria%2Ftypegraphql-nextjs-boilerplate","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftajpouria%2Ftypegraphql-nextjs-boilerplate/lists"}