typescriptdenooak

TypeError - Cannot read properties of undefined (reading 'name')


Basically I'm running into this error when trying to use UseCase class on Deno.

[uncaught application error]: TypeError - Cannot read properties of undefined (reading 'getAppInfosUseCase')

request: { url: "http://127.0.0.1:3030/", method: "GET", hasBody: false }
response: { status: 200, type: undefined, hasBody: false, writable: true }

    at handle (file:///C:/Users/joaop/Documents/dev/web/back-end/deno/shynotes-api%20(MVC)/src/useCases/GetAppInfos/GetAppInfosController.ts:11:39)
    at dispatch (https://deno.land/x/oak@v10.6.0/middleware.ts:41:13)
    at https://deno.land/x/oak@v10.6.0/router.ts:1148:20
    at dispatch (https://deno.land/x/oak@v10.6.0/middleware.ts:41:13)
    at composedMiddleware (https://deno.land/x/oak@v10.6.0/middleware.ts:44:12)
    at dispatch (https://deno.land/x/oak@v10.6.0/router.ts:1154:28)
    at dispatch (https://deno.land/x/oak@v10.6.0/middleware.ts:41:13)
    at composedMiddleware (https://deno.land/x/oak@v10.6.0/middleware.ts:44:12)
    at Application.#handleRequest (https://deno.land/x/oak@v10.6.0/application.ts:389:34)
    at Application.listen (https://deno.land/x/oak@v10.6.0/application.ts:559:28)

App Structure

project-structure

src/

app.ts

import { Application } from 'https://deno.land/x/oak@v10.6.0/mod.ts';
import { router } from './routes.ts';
import 'https://deno.land/x/dotenv@v3.2.0/load.ts';

const app = new Application();

app.use(router.routes());
app.use(router.allowedMethods());

export { app };

routes.ts

import { Router } from 'https://deno.land/x/oak@v10.6.0/router.ts';
import { getAppInfosController } from './useCases/GetAppInfos/index.ts';

const router = new Router();

router.get('/', getAppInfosController.handle);

export { router };

server.ts

import { app } from './app.ts';

await app.listen({
    port: 3030,
});

src/useCases/

GetAppInfosController.ts

import { Context } from 'https://deno.land/x/oak@v10.6.0/context.ts';
import { GetAppInfosUseCase } from './GetAppInfosUseCase.ts';

export class GetAppInfosController {
    constructor(
        private getAppInfosUseCase: GetAppInfosUseCase,
    ) {}

    handle(context: Context) {
        context.response.status = 200;
        return context.response.body = this.getAppInfosUseCase.execute();
    }
}

GetAppInfosUseCase.ts

export class GetAppInfosUseCase {
    execute() {
        return {
            author: 'Jphn',
            github: 'github.com/Jphn',
        };
    }
}

index.ts

import { GetAppInfosUseCase } from './GetAppInfosUseCase.ts';
import { GetAppInfosController } from './GetAppInfosController.ts';

const getAppInfosUseCase = new GetAppInfosUseCase();

const getAppInfosController = new GetAppInfosController(getAppInfosUseCase);

export { getAppInfosController, getAppInfosUseCase };

Solution

  • This question seems like you are in need of help debugging. I copied the data in your question to files in a directory on my file system to reproduce. Let's start with some basics:

    The version of Deno I'm using:

    % deno --version
    deno 1.24.0 (release, x86_64-apple-darwin)
    v8 10.4.132.20
    typescript 4.7.4
    

    Type-checking your code:

    % deno check src/server.ts 
    Download https://deno.land/x/dotenv@v3.2.0/load.ts
    Download https://deno.land/x/dotenv@v3.2.0/mod.ts
    Download https://deno.land/x/dotenv@v3.2.0/util.ts
    error: Module not found "file:///Users/deno/so-73142682/src/useCases/GetAppInfos/index.ts".
        at file:///Users/deno/so-73142682/src/routes.ts:2:39
    

    The first problem revealed is that there's an invalid module specifier in ./src/routes.ts:

    import { Router } from 'https://deno.land/x/oak@v10.6.0/router.ts';
    import { getAppInfosController } from './useCases/GetAppInfos/index.ts'; /*
                                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Unable to load a local module: "file:///Users/deno/so-73142682/src/useCases/GetAppInfos/index.ts".
      Please check the file path. deno(no-local) */
    
    const router = new Router();
    
    router.get('/', getAppInfosController.handle);
    
    export { router };
    
    

    The specifier just needs to be changed to "./useCases/index.ts". After making that change, let's type-check again:

    % deno check src/server.ts
    Check file:///Users/deno/so-73142682/src/server.ts
    

    Now type-checking passes: great. Let's try running the module:

    % deno run --allow-read=".env,.env.defaults" --allow-net="0.0.0.0:3030" src/server.ts
    

    The process doesn't terminate, so something's happening. Let's try making a GET request to the host. In a separate terminal shell:

    % deno eval --print "(await fetch('http://localhost:3030')).status"
    500
    

    The status of the request is 500. Let's switch back to the shell where the app is running and check if there's any output. There is:

    [uncaught application error]: TypeError - Cannot read properties of undefined (reading 'getAppInfosUseCase')
    
    request: { url: "http://localhost:3030/", method: "GET", hasBody: false }
    response: { status: 200, type: undefined, hasBody: false, writable: true }
    
        at handle (file:///Users/deno/so-73142682/src/useCases/GetAppInfosController.ts:11:45)
        at dispatch (https://deno.land/x/oak@v10.6.0/middleware.ts:41:13)
        at https://deno.land/x/oak@v10.6.0/router.ts:1148:20
        at dispatch (https://deno.land/x/oak@v10.6.0/middleware.ts:41:13)
        at composedMiddleware (https://deno.land/x/oak@v10.6.0/middleware.ts:44:12)
        at dispatch (https://deno.land/x/oak@v10.6.0/router.ts:1154:28)
        at dispatch (https://deno.land/x/oak@v10.6.0/middleware.ts:41:13)
        at composedMiddleware (https://deno.land/x/oak@v10.6.0/middleware.ts:44:12)
        at Application.#handleRequest (https://deno.land/x/oak@v10.6.0/application.ts:389:34)
        at Application.listen (https://deno.land/x/oak@v10.6.0/application.ts:559:28)
    

    Looking at the beginning of the stack trace shows that the error originated at this location:

    at handle (file:///Users/deno/so-73142682/src/useCases/GetAppInfosController.ts:11:45)
    

    The 11:45 means line 11, column 45. Let's check that position in the source module ./src/useCases/GetAppInfosController.ts:

    import { Context } from 'https://deno.land/x/oak@v10.6.0/context.ts';
    import { GetAppInfosUseCase } from './GetAppInfosUseCase.ts';
    
    export class GetAppInfosController {
        constructor(
            private getAppInfosUseCase: GetAppInfosUseCase,
        ) {}
    
        handle(context: Context) {
            context.response.status = 200;
            return context.response.body = this.getAppInfosUseCase.execute(); /*
                                                ~~~~~~~~~~~~~~~~~~
                                          Here's where the error occurred.
                                      `this` was `undefined` at the call site */
        }
    }
    
    

    The stack doesn't make this obvious, but the actual problem is where you use the handle method in ./src/routes.ts:

    import { Router } from 'https://deno.land/x/oak@v10.6.0/router.ts';
    import { getAppInfosController } from './useCases/index.ts';
    
    const router = new Router();
    
    router.get('/', getAppInfosController.handle); /*
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                    This is an unbound method call */
    
    export { router };
    
    

    Method binding has already been covered extensively on this site in other questions, so I won't reiterate the details here. You just need to bind the class instance object to the method so that when it's invoked as a function, the this value refers to the class instance object:

    router.get('/', getAppInfosController.handle.bind(getAppInfosController));
    //                                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    

    After saving those changes, let's stop the app using ctrl + c in the terminal and start it again:

    ^C
    
    % deno run --allow-read=".env,.env.defaults" --allow-net="0.0.0.0:3030" src/server.ts
    

    Now let's make another GET request to the host in a separate terminal shell, but this time we'll print the body as parsed JSON (because that's the expected response format):

    % deno eval --print "await (await fetch('http://localhost:3030')).json()"
    { author: "Jphn", github: "github.com/Jphn" }
    

    Now it looks like what's expected. Issue solved.