Graphql: Apollo Server 2.0

Created on 18 Jun 2018  路  27Comments  路  Source: nestjs/graphql

Does this package support Apollo Server 2.0 or the older version? I installed their release candidate for express (apollo-server-express@rc). graphqlExpress is no longer available. import { graphqlExpress } from 'apollo-server-express'; How would I go about using nestjs/graphql with Apollo Server 2.0?

thank you

feature

Most helpful comment

v2 is now stable, any advice on how to use it?

All 27 comments

The [email protected] is not stable yet. We'll support it once it hits the final release.

v2 is now stable, any advice on how to use it?

Yeah, I'll move to nest js until it support Apollo 2

I'm using this nestjs module with Apollo 2 in the following way:

app.module.js

import { Module } from '@nestjs/common';
import { GraphQLFactory, GraphQLModule } from '@nestjs/graphql';
import { ApolloServer } from 'apollo-server-express';
import { LostPetsResolver } from 'lost-pets/lost-pets.resolvers';
import { LostPetsService } from './lost-pets/lost-pets.service';

@Module({
  imports: [GraphQLModule],
  providers: [LostPetsService, LostPetsResolver], // Provide services and resolvers
})
export class AppModule {
  // Inject graphQLFactory as in Nestjs Docs
  constructor(private readonly graphQLFactory: GraphQLFactory) { }

  configureGraphQL(app: any) {
    // Same as nestjs docs - graphql guide
    const typeDefs = this.graphQLFactory.mergeTypesByPaths('./**/*.graphql');
    const schema = this.graphQLFactory.createSchema({ typeDefs });

    // this changed. Apollo lib internally apply app.use(...)
    // and other middlewares to work
    // but it needs app object
    const server = new ApolloServer({ schema });
    server.applyMiddleware({ app });
  }
}

And then I give the app object to this method:

main.ts

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // With Nestjs context get the app module to call our configure method
  const appModule = app.get(AppModule);
  appModule.configureGraphQL(app);

  await app.listen(3000);
}
bootstrap();

Thanks.

@diego-d5000 I haven't start with Nest yet but watching on it. So, based on your setup, how to expose graphql endpoint? Is it like:

  configure(consumer: MiddlewareConsumer) {
    consumer.apply(this.configureGraphQL).forRoutes('/graphql');
  }

@viiiprock You can't apply this configureGraphQL method in that way because isn't a middleware, it doesn't receive req,res,next params. I'm navigating through Apollo 2 code, but I don't find any middleware like function/method yet to do that. Default Apollo 2 route is "/graphql", but you could change it in applyMiddleware method:

const server = new ApolloServer({ schema });
const path = '/api/graphql';
server.applyMiddleware({ app, path });

great, I should make a try. Thanks @diego-d5000

@diego-d5000 do you have an issue with additional headers being sent when loading the graphql playground? I am seeing Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client in my app logs when visiting the URL the playground lives at.

@kyle-mccarthy I do, seems it came from playground, but you could try
```
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
const app = await NestFactory.create(AppModule);

// With Nestjs context get the app module to call our configure method
const appModule = app.get(AppModule);
appModule.configureGraphQL(app);
app.use('/graphql', () => {}); // Add route here
await app.listen(3000);
}
bootstrap();
````
And it will do the trick.
I don't use Playground anyway, Insomnia is better ;)

Nice. Thanks @viiiprock, It seems that it's a graphql-playground issue:
prismagraphql/graphql-playground#557

+1
v2 is now stable, any advice on how to use it?

@diego-d5000 wrote up how to do it, works like harm or just pull request to nest graphql module new version :)

@arjitkhullar Here is an example including subscription handling:

import { Module } from '@nestjs/common';
import { GraphQLFactory, GraphQLModule } from '@nestjs/graphql';
import { ApolloServer } from 'apollo-server-express';
import { LostPetsResolver } from 'lost-pets/lost-pets.resolvers';
import { LostPetsService } from './lost-pets/lost-pets.service';

@Module({
  imports: [GraphQLModule],
  providers: [LostPetsService, LostPetsResolver], // Provide services and resolvers
})
export class AppModule {
  // Inject graphQLFactory as in Nestjs Docs
  constructor(private readonly graphQLFactory: GraphQLFactory) { }

  configureGraphQL(app: any, httpServer: any) {
    // Same as nestjs docs - graphql guide
    const typeDefs = this.graphQLFactory.mergeTypesByPaths('./**/*.graphql');
    const schema = this.graphQLFactory.createSchema({ typeDefs });

    // this changed. Apollo lib internally apply app.use(...)
    // and other middlewares to work
    // but it needs app object
    const server = new ApolloServer({ schema });
    server.applyMiddleware({ app });
    server.installSubscriptionHandlers(httpServer);
  }
}
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // With Nestjs context get the app module to call our configure method
  const appModule = app.get(AppModule);
  const httpServer = app.getHttpServer();
  appModule.configureGraphQL(app, httpServer);

  await app.listen(3000);
}
bootstrap();

Unfortunately if you use nestjs/passport it can no extract headers from the execution context. Ending up an internal error
TypeError: Cannot read property 'headers' of undefined", " at JwtStrategy._jwtFromRequest (/Users/cschroeter/Workspace/Edomo/edomo-middleware/node_modules/passport-jwt/lib/extract_jwt.js:58:21)

@kamilmysliwiec v2 is now stable.

@cschroeter I encountered with the same issue when I tried to use @nestjs/passport. Just for test I implemented simple guard:

import { CanActivate, Injectable } from '@nestjs/common';

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(context) {
    const httpContext = context.switchToHttp();
    console.log(httpContext.getRequest());
    return true;
  }
}

and console.log output is undefined. By the same reason passport can't get headers from request. Do you have any solution? Thanks for help.

Apollo v2 integration is now available. Also, the docs + example have been updated as well https://docs.nestjs.com/graphql/quick-start

@cschroeter I encountered with the same issue when I tried to use @nestjs/passport. Just for test I implemented simple guard:

import { CanActivate, Injectable } from '@nestjs/common';

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(context) {
    const httpContext = context.switchToHttp();
    console.log(httpContext.getRequest());
    return true;
  }
}

and console.log output is undefined. By the same reason passport can't get headers from request. Do you have any solution? Thanks for help.

I have the same issue, any solutions for this?

@sofyanhadia just look at the docs https://docs.nestjs.com/graphql/tooling "Execution Context"

Using the AuthGuard within the GraphQL context is not possible. The context is not aware of any request object. So the JWT module tries to extract the Authorization header from the request object which is undefined. I'm not sure how to switch to original request object provided by the epxress instance, as already mentioned in https://github.com/nestjs/graphql/issues/48. So right now I don't see any possibility to guard your GraphQL endpoints, which is kinda bad, i guess

@cschroeter
I don't get it, why need instance? Didn't the Apollo provide original request object by the context ? With Nest, it wrapped around GraphQLModule.forRoot({...context: async({req}) => ..... })
And the AuthGuard is really simple to implement.

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const ctx = GqlExecutionContext.create(context);
    // you can get context by using `ctx.getContext()`
    return ctx && true;
  }
}

before update, I did something quite tricky, but it worked like a charm

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {

    // => get the context object with `context.getArgs()[2]`;

  }

@viiiprock Sure you can access your context within the AuthGuard - no problem. But you can not access your request headers for example. You would need to extract them in the ApplicationModule from the req and then implement your authentication by urself instead of reusing the @nestjs/passport and @nestjs/jwtmodule.

got it , I don't use those stuff, I implemented jwt my self with jsowebtoken

@cschroeter there's how I've upgraded:

import * as jwt from 'jsonwebtoken';
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';

@Injectable()
export class AuthGuard implements CanActivate {
    constructor(private readonly authService: AuthService) {}

    canActivate(
        context: ExecutionContext,
    ): boolean | Promise<boolean> | Observable<boolean> {
        const ctx = GqlExecutionContext.create(context);
        const request = ctx.getContext().request;

        const token = request.headers.authorization
            ? (request.headers.authorization as string).split(' ')
            : null;

        if (process.env.NODE_ENV === 'development') {
            return true;
        }

        if (token && token[1]) {
            const decoded: any = jwt.verify(token[1], process.env.SECRET);

            return !!this.authService.findById(decoded.id);
        }
    }
}
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
@Module({
    imports: [
        GraphQLModule.forRoot({
            typePaths: ['./**/*.gql'],
            debug: true,
            playground: true,
            tracing: true,
            path: '/api/graphql',
            context: ({ req, res }) => ({
                request: req,
            }),
            rootValue: ({ req }) => ({ req }),
        }),
    ],
})
export class GQLModule {}

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

Was this page helpful?
0 / 5 - 0 ratings