Determine data types for functions in individual files when using ElysiaJS

Currently, I am utilizing ElysiaJS to establish an API. The code can be found in the following open-source repository here.

In my setup, there are three essential files: auth.routes.ts, auth.handlers.ts, and auth.dto.ts. The routes file contains the path, validation object, and handler function. The validation object is exported from the auth.dto.ts (data transfer object), while the handler function, which encompasses all logic and queries, is exported from the auth.handler.ts.

auth.routes.ts

const authRoutes = new Elysia({ prefix: '/auth' })
  /** Injecting Database Connection */
  .use(databaseConfig)
  /** Register User with Email and Password */
  .post('/register', registerWithEmailAndPassword, registerWithEmailAndPasswordDTO)

export { authRoutes }

The databaseConfig serves as an Elysia Plugin containing the database connection code.

auth.dto.ts

import { t } from "elysia"

export const registerWithEmailAndPasswordDTO = {
  body: t.Object({
    email: t.String({ format: 'email' }),
    password: t.String({ minLength: 6 }),
  })
}

auth.handlers.ts

/** Importing Schema */
import { type Context } from "elysia"

import Schema from "../../schema"
import { hashPassword } from "../../utils/auth.utils"

/** Destructuring Schema */
const { users } = Schema

export const registerWithEmailAndPassword = async (context: Context): Promise<string> => {
  const { set, body: { email, password }, db } = context

  // more code here
}

If the handler function resides within the 'auth.routes.ts' file, all relevant types are injected by Elysia, resulting in everything functioning as expected. However, when the handler function is placed in a separate file, adding the Context to the argument type does not effectively add types for the body parameters injected by the validator object nor the database config object added as a plugin.

Is there a way to correctly infer these types in the handler function present in a distinct file?

Edit

To tackle this issue, constructing the type manually worked seamlessly. Here's the code snippet:

import { dbType } from "../../config/database.config"
type RegisterType = Context & {
  body: {
    email: string
    password: string
  },
db: () => dbType
}

The RegisterType will then be utilized in the handler function like so:

export const registerWithEmailAndPassword = async (context: RegisterType)
.

database.config.ts

// create the connection
const connection = connect({
  host: process.env.DB_HOST,
  username: process.env.DB_USERNAME,
  password: process.env.DB_PASSWORD
})

const db = drizzle(connection, { schema, logger })
const databaseConfig = new Elysia({ name: "databaseConfig" })
  .decorate('db', () => db)

export type dbType = typeof db
export default databaseConfig

Edit 2

Despite following all steps outlined in the Dependency Injection documentation, the issue still persists.

Answer №1

Elysia's Inability to Type External Route Handlers

Although I followed the same pattern as you, when route Handlers are kept separate from the actual route definition, Elysia lacks the capability to extend types to the handlers. Here is a simple code example for better understanding:

// src/routes/index.ts

import { Elysia } from 'elysia'
import databaseConfig from 'src/lib/database'
import { testHandler } from 'src/routes/handlers'
import { testBodySchema } from 'src/schema'

const routes = new Elysia()
  .use(databaseConfig)
  .post('/test', testHandler, {
    body: testBodySchema
  })

export default routes

When it comes to our handlers in the index.ts file, Elysia fails to transfer the types from the routes file:

// src/routes/handlers/index.ts

export const testHandler = async ({ body, database }) => {
  // The 'body' and 'database' variables here will not have designated types
}

Is there a way to initialize routes externally with Elysia without compromising types?

The good news is that there is a more suitable approach in alignment with Elysia's design philosophy.

In the modified routes index.ts file, instead of defining routes directly, we now receive the route definitions from an external source:

// src/routes/index.ts

import { Elysia } from 'elysia'
import authRoutes from 'src/routes/auth'
import anotherRouteGroup from 'src/routes/another'

const routes = new Elysia()
  .use(authRoutes)
  .use(anotherRouteGroup)

export default routes

All authentication-related definitions are moved inside the auth index.ts file, where we create another Elysia instance specifically for handling auth routes (the recommended Elysia approach):

Creating a new Elysia instance takes approximately 8ms, so it has minimal impact on performance.

saltyaom Quote from discord chat

https://i.stack.imgur.com/xUTtk.png

Now, let's focus on the auth routes. Defining route handlers inline enables Elysia to handle type propagation seamlessly:

// src/routes/auth/index.ts
import { Elysia } from 'elysia'
import databaseConfig from 'src/lib/database'
import { loginBodySchema } from 'src/schema'

const routes = new Elysia({ prefix: '/auth' })
  .use(databaseConfig)
  .post('/login', ({ body, database }) => {
    // 'body' and 'database' variables are now accurately typed!
  }, {
    body: loginBodySchema
  })
  .post('/register', () => {})

export default routes

Answer №2

Take a look at the following:

// auth.dto.ts
const registerWithEmailAndPasswordDTO = t.Object({
    email: t.String({ format: 'email' }),
    password: t.String({ minLength: 6 }),
})

export type RegisterWithEmailAndPasswordDTO = Static<typeof registerWithEmailAndPasswordDTO>

export const authModel = new Elysia({ name: 'authModel' })
    .model({
        registerWithEmailAndPasswordDTO,
    })


// auth.service.ts
export const authService = new Elysia({ name: 'authService' })
    .derive(({ set }) => ({
        registerWithEmailAndPassword: async (dto: RegisterWithEmailAndPasswordDTO) => {
            // register user here

            // can use set object here
            set.status = 500;
        }
    }))

// auth.controller.ts
export const authController = new Elysia({ prefix: '/auth' })
    .use(authModel)
    .use(authService)
    .post("/register", ({ registerWithEmailAndPassword, body }) => registerWithEmailAndPassword(body),
        { body: "registerWithEmailAndPasswordDTO" })

With the .use method, you have the ability to access one plugin's contents from another in a safe and typesafe manner:

  • When defining models with .model in a plugin, schemas can be shared by use-ing that plugin
  • By creating context-aware functions using .derive, these functions can also be shared in the same way without sharing any state across multiple requests

Don't forget to join the Elysia discord for faster support and interactions with other users!

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Change the format of the array from the initial array to the new array structure

I have a multi dimensional array that I need to convert into the array2 structure. I've tried various methods, but all of them have resulted in bulky code and lots of iteration. Is there an easier way to accomplish this task? I am new to Angular and ...

Retrieve a static property from a specific type

I've encountered a dilemma with the code snippet below: class Action { public static DEPENDENCIES: (typeof Action)[] = []; public static MIN_USES: number | null = null; public static MAX_USES: number | null = null; } class SomeAction ext ...

Disabling a Field in Angular While Preserving its Value

Hey there, late night folks! I have a quick question about Angular. I'm working with a form that includes a specific field where I set the value using patchValue, but I also need to disable this field. The issue is that after disabling it, the value i ...

Using a template reference variable as an @Input property for another component

Version 5.0.1 of Angular In one of my components I have the following template: <div #content>some content</div> <some-component [content]="content"></some-component> I am trying to pass the reference of the #content variable to ...

Workspace Settings cannot be saved due to an unregistered configuration

I've been attempting to change the StatusBar color in VScode Setting.json using Configuration and Workspace. However, I encountered an error when trying to make the update: Error: Unable to write to Workspace Settings because workbench.colorCustomizat ...

Error in TypeScript: Angular Jasmine - The argument given as type 'string' cannot be assigned to a parameter expecting type 'never'

Currently, I am in the process of writing test cases for Angular using Jasmine 3.6.0 and TypeScript 4.1.5 with "strict": false set in my tsconfig.json file. One particular task involves spying on a component method called 'close', and following ...

Enhancing Angular Material: requiring more user engagement for rendering to occur

Encountering an unusual issue with Angular Material: certain components require an additional event, like a click or mouse movement on the targeted div, to trigger the actual rendering process. For instance, when loading new rows in mat-table, some empty ...

The power of Ionic 2 combined with the Web Audio API

I am currently developing an Ionic 2 application that requires access to the user's microphone. When working on a web platform, I would typically use the following code snippet to obtain microphone access. navigator.getUserMedia = (navigator['ge ...

Error in Typescript: Function expects two different types as parameters, but one of the types does not have the specified property

There's a function in my code that accepts two types as parameters. handleDragging(e: CustomEvent<SelectionHandleDragEventType | GridHandleDragEventType>) { e.stopPropagation(); const newValue = this.computeValuesFromPosition(e.detail.x ...

Pulling the month name based on a specific year and week using Javascript

In my HTML form, there are two fields called Year and Week. When the user chooses a Year and Week from the dropdowns, I would like to show the corresponding Month Name for that specific year and week. Is there anyone who can assist me in retrieving the m ...

Error: Unable to set value, val.set is not a defined function for this operation (Javascript

Encountering a problem while running the function val.set(key, value), resulting in a type error TypeError: val.set is not a function within the file vendor-es2015.js. Here's the simplified code snippet: import { Storage } from '@ionic/storage& ...

Creating custom functionality by redefining methods in Typescript

My current scenario is as follows: abstract class A implements OnInit{ ngOnInit() { this.method(); } private method() { // carrying out tasks } } class B extends class A implements OnInit { ngOnInit() { thi ...

When iterating through a table, an error occurs stating that the property "rows" is not available on type HTMLElement (

Issue Error TS2339 - Property 'rows' does not exist on type HTMLElement when looping through table in Angular 7 Encountering error when trying to loop through HTML table in Angular 7 Currently working with Angular 7 and facing an error while ...

Angular: Refresh mat-table with updated data array after applying filter

I have implemented a filter function in my Angular project to display only specific data in a mat-table based on the filter criteria. Within my mat-table, I am providing an array of objects to populate the table. The filtering function I have created loo ...

Incorporate the ID of a newly created document into another document using Mongoose

Is there a way in mongoose to save the id of one document after creation into another document within the same collection using just a single query? ...

Combining namespaces in Typescript declaration files

Currently, I am attempting to combine namespaces from d.ts files. For example, when I attempt to merge namespaces in a single file, everything works as expected. declare namespace tst { export interface info { info1: number; } var a: ...

Viewing the photo container before uploading while having text overlap

I'm encountering an issue where the image previews are overlapping with the text in another div. Here are the screenshots: the first one shows how it looks before the preview, and the second one shows what happens when images are added: https://i.sst ...

Utilizing the get and set methods to alter the structure of a string, but encountering the issue where the set method is

Upon receiving a datetime through an HTTP request, I need to format it before utilizing it. To achieve this, I utilize the get and set methods in my code. However, I noticed that the set method is never invoked. This is how my component (AdminComponent) l ...

The validator function in FormArray is missing and causing a TypeError

I seem to be encountering an error specifically when the control is placed within a formArray. The issue arises with a mat-select element used for selecting days of the week, leading to the following error message: What might I be doing incorrectly to tri ...

Tips for successfully typing the backtick character when transitioning to Typescript:

I am currently working on a Typescript Vue project involving Leaflet. I came across some code for lazy-loading map markers, but it was written in Javascript. Although the code works fine, I keep receiving errors and warnings from VSCode because this is not ...