Why and how we migrated from TypeGraphQL to NestJS GraphQL?

The benefits and challenges of transitioning from TypeGraphQL to NestJS's GraphQL module

GraphQL has become a popular choice for building APIs, and it’s not hard to see why. With its strong typing system and ability to retrieve only the data you need, it has many advantages over traditional REST APIs. However, choosing the right tools to build your GraphQL API can be challenging.

NestJS is an awesome Node.js framework that lets you build GraphQL APIs, REST APIs, and more. In this article, we will explore the reasons why we migrated from TypeGraphQL (type-graphql and typegraphql-nestjs) to NestJS GraphQL (@nestjs/graphql). We will examine the maintenance problems and lacking features of the former packages and provide step-by-step instructions on how to migrate to the latter. Whether you are planning to create a new NestJS project or already have an existing one, this article will provide valuable insights on how to choose the right tools.

Disclaimer

We completed the migration on 25 October 2022. The maintenance status of both packages, feature differences, etc. may change over time. Implementation details and the migration guide were prepared according to the following versions:

The problems with TypeGraphQL

While TypeGraphQL is a powerful tool for building GraphQL APIs, we encountered several issues during our implementation. One of the most significant challenges we faced was the lack of documentation/examples about alternatives to NestJS-specific features and community support. Although TypeGraphQL has gained popularity in recent years, we struggled to find resources and examples to guide us through the development process.

While typegraphql-nestjs offers alternative solutions for features such as guards and interceptors, we found that they were not as feature-rich or well-maintained as the built-in solutions provided by NestJS. This made it challenging to implement more advanced aspect-oriented features in our application. @nestjs/passport module is a clear example of this problem: it’s a great module that is pretty straightforward to set up when using the built-in NestJS package, but it’s not possible to use it when using typegraphql-nestjs due to lack of integration. Since NestJS also offers REST API, some microservices, etc. if you want them alongside your GraphQL API, you will end up duplicating authorization, middleware, etc. logic and they might not even have feature parity.

Despite these challenges, we overcame them and successfully implemented our GraphQL API using TypeGraphQL. However, we believe it’s essential to be aware of this tool’s potential pitfalls and limitations to make informed decisions about its usage in future projects.

After getting slowed down and not being able to implement a clean solution multiple times, being held back on not being able to upgrade graphql or NestJS due to lack of support, and thinking this situation wouldn’t improve any time soon, we finally decided it’s time to move away from TypeGraphQL to NestJS GraphQL solution. Let’s move on with the migration steps.

Remove the packages

First, remove type-graphql and typegraphql-nestjs so it’s easier to spot the places that need updates because you can trace where they are used through the generated errors.

# pnpm
pnpm remove type-graphql typegraphql-nestjs
# yarn
yarn remove type-graphql typegraphql-nestjs
# npm
npm uninstall type-graphql typegraphql-nestjs

It isn’t possible to have both packages working together nicely: even if it isn’t ideal, especially for large applications, you’ll have to migrate in one go.

Update module names in imports

Replace 'type-graphql' with '@nestjs/graphql' and 'typegraphql-nestjs' with '@nestjs/graphql' in all import statements. This will narrow the compiler/lint problems down to specific import members instead of full modules.

To replace usage:

  1. Enable “match whole word” and “match case” on the search and replace.
  2. Search for 'type-graphql' and replace it with '@nestjs/graphql'
  3. Search for 'typegraphql-nestjs' and replace it with '@nestjs/graphql'

Quasar framework QuasarConf 2022 Yusuf Kandemir talk

Example search&replace usage for VS Code
Update imports and usage

Field resolvers
Replace FieldResolver with ResolveField in all import statements and usage. They share identical signatures, so a simple search and replace is enough.

To replace usage:

  1. Enable “match whole word” and “match case” on the search and replace.
  2. Search for FieldResolver and replace it with ResolveField.

Arguments
The way @Args works with multiple arguments through @ArgsType is the same for both packages:

@Args() { id, name }: GetSomethingArgs

However, specifying individual arguments is different. Replace @Arg with @Args in all import statements and usage. Their signatures are different, so doing a search&replace will be a bit more difficult.

type-graphql:

@Arg('id')
@Arg('id', () => ID)
@Arg('id', () => ID, { nullable: true })

@nestjs/graphql:

@Args('id')
@Args('id', { type: () => ID })
@Args('id', { type: () => ID, nullable: true })

To replace usage:

  1. Enable “use regular expressions” on the search and replace.
  2. Replace usage with one parameter:
    • Search for @Arg\('(.*)'\)
    • Replace with @Args('$1')
  3. Replace usage with three parameters:
    • Search for @Arg\((.*), (.*), \{ (.*) \}\)(\s?\w+\?:)?
    • Replace with @Args($1, { type: $2, $3 })$4
  4. Replace usage with two parameters:
    • Search for @Arg\((.*), (\(.*?\)) => (\w+)\)(\s?)
    • Replace with @Args($1, { type: $2 => $3 })$4
  5. Replace imports:
    • Enable “match whole word” and “match case” on the search and replace.
    • Search for Arg and replace it with Args.

Modules
The module names and options are different. There will likely be only one reference, and it will be in src/app.module.ts.

Replace all references of TypeGraphQLModule with GraphQLModule manually or use the search and replace feature.

Update dateScalarMode option to buildSchemaOptions.dateScalarMode:

{
-   dateScalarMode: ...,
+   buildSchemaOptions: {
+     dateScalarMode: ...,
+   },
}

If you were using emitSchemaFile conditionally with TypeGraphQL, replace it with autoSchemaFile and typeDefs options. The main difference is that it doesn’t read the schema if autoSchemaFile is false, so you have to read and provide it yourself through typeDefs. Here is an example:

import { readFile } from 'node:fs/promises';
import { GraphQLModule } from '@nestjs/graphql';

// ...
GraphQLModule.forRootAsync({
  inject: [rootConfiguration.KEY],
  useFactory: async (config: RootConfiguration) => {
    const schemaPath = join(process.cwd(), 'src/schema.graphql');
    return config.generateSchema
      ? {
        autoSchemaFile: schemaPath,
      }
      : {
        autoSchemaFile: false,
        typeDefs: await readFile(schemaPath, 'utf8'),
      },
  },
}),
// ...

Parameter decorators
They have similar logic, but the usage is noticeably different. Let’s go over an example to understand the main differences and decide on a migration route. We will have a decorator called @CurrentUserId which takes userId from the context and injects it into the decorated parameter.

Custom context type structure and import are common between the two packages:

// src/app.module.ts
export interface GraphQLContext {
  userId?: number;
}

type-graphql:

import { createParamDecorator } from 'type-graphql';
import { GraphQLContext } from 'src/app.module';

export function CurrentUserId() {
  return createParamDecorator<GraphQLContext>(({ context }) => context.userId);
}

@nestjs/graphql:

import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { GraphQLContext } from 'src/app.module';

export const CurrentUserId = createParamDecorator(
  (_data: never, executionContext: ExecutionContext) => {
    const gqlExecutionContext = GqlExecutionContext.create(executionContext);
    const context = gqlExecutionContext.getContext<GraphQLContext>();
    return context.userId;
  }
);

The type-graphql way uses a function returning the decorator handler while the NestJS way uses a variable to store the decorator itself. Due to this difference, the decorator parameters in the latter case are provided as the first parameter, named data, in the custom decorator callback. In our example, the type of the _data parameter is never as our custom decorator doesn’t have any parameters.

As you can see, getting the GraphQL context in the NestJS version takes extra effort. This is to be expected because NestJS decorators can also be used for REST API, microservices, etc.

Conclusion

The maintenance problems, lack of documentation/examples, and limited community support for NestJS-specific features made it hard for us to move forward with TypeGraphQL. Even though it didn’t completely block us, it slowed us down considerably. We migrated to NestJS GraphQL solution because it offered more built-in features and better integration with the NestJS ecosystem, allowing us to move faster.

I hope you found this article useful. Hopefully, this saved you some time avoiding the pitfalls we got in. Get in touch if you need help doing a code migration, if you need a consult on which packages to use for your GraphQL tooling, or if you had a different experience than ours and you want to share it with us!

If you need help or a technical consultancy remember that you can visit our Github Sponsorship page. You can become a Dreamonkey sponsor and we will give you the support you need. Otherwise you can contact us directly.