tsedio / tsed

:triangular_ruler: Ts.ED is a Node.js and TypeScript framework on top of Express to write your application with TypeScript (or ES6). It provides a lot of decorators and guideline to make your code more readable and less error-prone. ⭐️ Star to support our work!
https://tsed.io/
MIT License
2.85k stars 285 forks source link

TsEd with apollo server 4 support #2493

Open SavvasAmruthShivaprakash opened 1 year ago

SavvasAmruthShivaprakash commented 1 year ago

Is your feature request related to a problem? Please describe.

Migrating from apollo3 to apollo4 , the current dependencies are all compliant with apollo3.

Describe the solution you'd like

No response

Describe alternatives you've considered

No response

Additional context

No response

Acceptance criteria

No response

Romakita commented 1 year ago

Hello @SavvasAmruthShivaprakash

you can create a PR if you have time to update dependencies :)

See you Romain

alecl commented 5 months ago

While the Apollo 4 EoL was extended for a bit, it is coming up again.

Romakita commented 5 months ago

Hi everybody,

PR is welcome to update dependencies.

See you

Romakita commented 5 months ago

https://www.apollographql.com/docs/apollo-server/migration/

Romakita commented 4 months ago

PR is ready: https://github.com/tsedio/tsed/pull/2713

@alecl @SavvasAmruthShivaprakash Can you review the PR. Also the migration to Apollo 4 change a lot of things so I haven't choice to broke the module configuration.

I don't want to create major release for that :/

SavvasManteshPatil commented 2 months ago

Hi @Romakita Getting below error while building the monorepo

Lerna (powered by Nx) Running target build for 58 projects failed

Tasks not run because their dependencies failed or --nx-bail=true:

SavvasManteshPatil commented 2 months ago

@Romakita, Finding an issue while implementing PR 2713. I am not able load playground after server start. my server configuration is as below and package and code changes are here

@Configuration({
    mount: {
        '/': [ HealthCheckController ],
    },
    apollo: {
        server1: {
            path: '/url',
            includeStacktraceInErrorResponses: true,
            debug: isStackTraceEnabled,
            playground: true,
            resolvers: [
               /* resolvers list */
            ],
            buildSchemaOptions: {
                authChecker: customAuthChecker,
                emitSchemaFile: true
            },
            plugins: [setHttpPlugin],
            formatError: (formattedError: GraphQLFormattedError, error: unknown) => {  }
        }
    },
    httpPort: config.get('port'),
    httpsPort: false,
    logger: {
        logRequest: false
    },
    env: config.get<boolean>('production') ? Env.PROD : Env.DEV
})
Romakita commented 2 months ago

Hi @SavvasAmruthShivaprakash How have you installed package? because the new package isn't available on npm right know ^^

Romakita commented 2 months ago

Finding an issue while implementing PR https://github.com/tsedio/tsed/pull/2713. I am not able load playground after server start. my server configuration is as below and package and code changes are here

Your doc isn't public

SavvasManteshPatil commented 2 months ago

Finding an issue while implementing PR #2713. I am not able load playground after server start. my server configuration is as below and package and code changes are here

Your doc isn't public

updated the link please check now

SavvasManteshPatil commented 2 months ago

Hi @SavvasAmruthShivaprakash How have you installed package? because the new package isn't available on npm right know ^^

Have pulled the feature branch and built locally. referring @tsed/apollo,@tsed/typegraphql and @tsed/di packages from local directory by updating path in package.json

Romakita commented 2 months ago

Ok @SavvasAmruthShivaprakash I see. Your test wasn't executed in a normal condition, links modules can cause many errors due to the DI implement and javascript (node module resolutions). In your case, You haven't guarantees to have only one DI injector service imported in the code. And the branche has some ts issue (related to the last rebase).

So I prefer to fix ts issue on the branche and give RC version of the module to test correctly the new packages.

Se you. ;)

Romakita commented 2 months ago

@SavvasAmruthShivaprakash new packages are now available under v7.81.0-rc.1 ;) enjoy it!

SavvasManteshPatil commented 1 month ago

Hi @Romakita I have tried installing all packages from shared RC version, finding an issue while installing @tsed/apollo. attached error log below.

npm i --save @tsed/apollo@7.81.0-rc.1 npm error code E404 npm error 404 Not Found - GET https://registry.npmjs.org/@apollo%2fdatasource-http - Not found npm error 404 npm error 404 '@apollo/datasource-http' is not in this registry. npm error 404 npm error 404 Note that you can also install from a npm error 404 tarball, folder, http url, or git url. npm error A complete log of this run can be found in: C:\Users\mantesh.patil\AppData\Local\npm-cache_logs\2024-09-05T10_37_02_380Z-debug-0.log

Romakita commented 1 month ago

Ok I'll publish a new release to fix that :)

Romakita commented 1 month ago

@SavvasAmruthShivaprakash fixed and released :)

SavvasManteshPatil commented 1 month ago

Hi @Romakita I have tried implementing new package, can install packages and run the application. I am trying to alter context using $alterApolloContext hook like bellow. $alterApolloContext(context: ApolloContext, $ctx: PlatformContext) { return ({ ...context, authorization: $ctx.getRequest().headers.authorization, userId: $ctx.getRequest().headers['user-id'], orgId: $ctx.getRequest().headers['org-id'], isAuthenticated: $ctx.getRequest().authenticated, userScope: $ctx.getRequest().userScope, error: $ctx.getRequest().error, logger: $ctx.getRequest().logger }); }

where server throwing an authentication error "errors": [ { "message": "Not authenticated.", "path": [ //Query name ], "extensions": { "code": "INTERNAL_SERVER_ERROR", "status": 500 } } ], "data": null }

Romakita commented 1 month ago

@SavvasAmruthShivaprakash your stack error isn’t enougth to investigate. Can please create reproducible repo example?

SavvasManteshPatil commented 1 month ago

@Romakita Can you please have look at server.ts appended below and let me know any modification required. Basically it is failed to create the context.

import '@tsed/ajv';
import { Configuration, Constant, Inject, PlatformApplication, PlatformContext } from '@tsed/common';
import { Env } from '@tsed/core';
import { Logger } from '@tsed/logger';
import '@tsed/platform-express'; // /!\ keep this import
import '@tsed/typegraphql';
import bodyParser from 'body-parser';
import compress from 'compression';
import config from 'config';
import cookieParser from 'cookie-parser';
import cors from 'cors';
import { NextFunction } from 'express';
import methodOverride from 'method-override';

import { ApolloContext } from '@tsed/apollo';
import { GraphQLFormattedError } from 'graphql';
import { StatusCodes } from 'http-status-codes';
import { customAuthChecker } from './auth-checker/custom-auth.checker';
import { HealthCheckController } from './controllers/health-check.controller';
import { HelloController } from './controllers/helloController';
import { configureHttp, logAgentDetails } from './http/configure-http';
import { DEBUG_LOGGING, KEEP_ALIVE_OPTIONS } from './model/common/const';
import { CONFIG } from './services/config/config.provider';
import { LOGGER } from './utils/logger';

const rootDir = process.cwd();
const isStackTraceEnabled = process.env.STACK_TRACE_ENABLED === 'true';
const setHttpPlugin = {
    async requestDidStart() {
        return {
            async willSendResponse(Response: any): Promise<void> {
                const { response } = Response;
                const ErrorResponse = response?.errors?.[0]?.extensions;
                if (ErrorResponse) {
                    response.http.status = ErrorResponse?.response?.status || ErrorResponse?.status;
                }
            }
        };
    }
};

@Configuration({
    mount: {
        '/': [HealthCheckController, HelloController],
    },
    apollo: {
        server1: {
            path: '/graphql',
            includeStacktraceInErrorResponses: true,
            debug: isStackTraceEnabled,
            playground: true,
            resolvers: [
                //Resolvers List
            ],
            buildSchemaOptions: {
                authChecker: customAuthChecker,
                emitSchemaFile: true
            },
            plugins: [setHttpPlugin],
            formatError: (formattedError: GraphQLFormattedError, error: unknown) => {
                let extensions: any = {};
                if (formattedError.extensions && formattedError.extensions.code) {
                    let statusCode;
                    switch (formattedError.extensions.code) {
                        case 'INTERNAL_SERVER_ERROR':
                            statusCode = StatusCodes.INTERNAL_SERVER_ERROR;
                            break;
                        case 'GRAPHQL_VALIDATION_FAILED':
                            statusCode = StatusCodes.BAD_REQUEST;
                            break;
                        case 'FORBIDDEN':
                            statusCode = StatusCodes.FORBIDDEN;
                            break;
                        default:
                            if (Number(formattedError.extensions.code) || formattedError.extensions.status) {
                                statusCode = formattedError.extensions?.status || formattedError.extensions?.code;
                            } else {
                                // Default to a sensible status code
                                statusCode = StatusCodes.BAD_REQUEST;
                                // Add a flag to indicate a malformed error
                                extensions.malformedError = true;
                            }
                    }
                    extensions = { ...extensions, code: formattedError.extensions.code, status: statusCode };
                }
                if (formattedError.extensions && formattedError.extensions.status) {
                    extensions = { ...extensions, status: formattedError.extensions.status };
                }
                if (formattedError.extensions && formattedError.extensions.response) {
                    extensions = { ...extensions, response: formattedError.extensions.response };
                }
                if (formattedError.extensions && formattedError.extensions.exception) {
                    let processedException = {};
                    if (formattedError.extensions.stacktrace) {
                        processedException = { stacktrace: formattedError.extensions.stacktrace };
                    }
                    extensions = { ...extensions, exception: processedException };
                }
                /**
                 * TODO: We might want to go in the path of doing error masking for some error messages,  
                 * to prevent leaking some internal information.
                 * https://learning.postman.com/open-technologies/blog/graphql-error-handling/
                 */
                return { message: formattedError.message, path: formattedError.path, extensions };
            }
        }
    },
    httpPort: config.get('port'),
    httpsPort: false,
    logger: {
        logRequest: false
    },
    env: config.get<boolean>('production') ? Env.PROD : Env.DEV
})

export class Server {

    @Inject()
    private readonly app: PlatformApplication;

    @Inject(LOGGER)
    private readonly logger: Logger;

    @Inject(CONFIG)
    private readonly config: any;

    @Constant("PLATFORM_NAME")
    platformName: string;

    $alterApolloContext(context: ApolloContext, $ctx: PlatformContext) {
        return ({
            ...context,
            authorization: $ctx.getRequest().headers.authorization,
            userId: $ctx.getRequest().headers['user-id'],
            orgId: $ctx.getRequest().headers['org-id'],
            isAuthenticated: $ctx.getRequest().authenticated,
            userScope: $ctx.getRequest().userScope,
            error: $ctx.getRequest().error,
            logger: $ctx.getRequest().logger
        });
    }

    public $beforeRoutesInit(): void {
        configureHttp(this.config.get(KEEP_ALIVE_OPTIONS));
        this.app
            .use(cors())
            .use(cookieParser())
            .use(compress({}))
            .use(methodOverride())
            .use(bodyParser.json())
            .use(
                bodyParser.urlencoded({
                    extended: true
                })
            )
            .use(async (req: any, res: Response, next: NextFunction) => {
                req.logger = this.logger;
                if (this.config.get(DEBUG_LOGGING)) {
                    logAgentDetails();
                }
                next();
            });
    }
}
Romakita commented 1 month ago

@SavvasAmruthShivaprakash Please create small repository to reproduce that ! and steps reproduction!

SavvasManteshPatil commented 1 month ago

@SavvasAmruthShivaprakash Please create small repository to reproduce that ! and steps reproduction!

I have created a repo @ https://github.com/ManteshP/tsed-sample.

SavvasAmruthShivaprakash commented 1 month ago

@Romakita, Looks like issue is not resolved. It's just validating the query. I have reinstalled all the dependencies and checked it is failing to create the context since context value is undefined I am not able to access the data source supposed to be available the context.

"stacktrace": [ "TypeError: Cannot read properties of undefined (reading 'getStudentSessionUsage')", " at StudentSessionUsageThisWeekResolver.getStudentSessionUsageThisWeek (C:\SA

Romakita commented 1 month ago

Yes but you haven’t given the step reproduction…

Romakita commented 1 month ago

Reproduced. Your issue occurs because, your datasource aren't imported in your code.

console.log('====> StudentSessionUsageService') // not displayed in terminal.

@DataSource()
export class StudentSessionUsageService extends BearerRestDataSource {}

Please take a time to fix that and push changes. You have a lot of imports issue in your reproducible repository. That's why I insist on having a repository with the minimum of code! You put too much and you have errors here and there (dependency problems, import problems and code problems), it makes debugging more complicated ;) And for now, I have nothing to fix on my side :D

SavvasManteshPatil commented 1 month ago

@Romakita I revisited the code and observed that datasources are not imported, after some code correction it is working as expected. Thank you 👍

SavvasManteshPatil commented 1 month ago

@Romakita we have integrated v8.0.0-alpha.5 version and it's working as expected. As we see your roadmap you are moving from commonJs to esm syntax which is breaking change for us. Can you please release a tag version for v8.0.0-alpha.5.

Romakita commented 1 month ago

Hi @SavvasAmruthShivaprakash What do you mean by "Can you please release a tag version for v8.0.0-alpha.5. ?"

SavvasManteshPatil commented 1 month ago

Hi @Romakita I mean, could you provide a release for Apollo Server migration changes using CommonJS before transitioning to ESM syntax?

Romakita commented 1 month ago

Hi @SavvasAmruthShivaprakash

I can't publish v7 with Apollo 4, the package introduce breaking change. I won't plublish a v8 with cjs support.

So you have different solutions:

See you ;)