-
-
Notifications
You must be signed in to change notification settings - Fork 438
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feature-request: Add Nest js support #171
Comments
@Bekacru i have an idea on how to "implement" without modifying the current code (only docs) The only thing is to create a controller and module and get the |
yes feel free to open a pr. and we're not going to have integration we only need the docs. |
Ok! I will try to have the nestjs docs soon 🙌🏼🚀 |
Any progress on this? |
@ismoiliy98 join us in discord |
I tried adding better auth to nest but the ESM nature of better auth is nor playing nicey with NestJS. @danyalutsevich which discord do i need to join to see the discusiion? |
@BayBreezy you can check the repo from this #359 issue discord: https://discord.com/invite/GYC3W7tZzb |
I've tried to get the migration done and working in nest js. |
@Innei , so you compiled inside your code? or compiled to another package? What is the way? |
complied sometimes has to be done, in writing nodejs server a few used pkg does not provide cjs export, so need to be converted. Create a separate subpackage dedicated to compiling these packages using a monorepo. |
Are there plans on the nest.js side to move to esm? That would seem like the more healthy fix since the whole ecosystem is moving that direction. In particular since also typescript is starting to think about deprecating and removing features nest.js relies on, e.g. useDefineForClassFields: false and probably later on experimentalDecorators: true. See: microsoft/TypeScript#45995 (comment). If nest.js doesn't want to be left back in the dark there is gonna have to be some movement soon anyway towards esm and standard decorators. |
@marcomuser Unfortunately, no 😞 |
Any updates on this? Is there rough eta for nestjs support? |
We out here waiting patiently @ddedic |
Oh man I was so excited to integrate better-auth but was disappointed that it does not support Nest.js. |
could anyone please confirm me if there are still issue with nest js support? |
@Bekacru I am still investigating. I was able to get most endpoints working by adding this middleware import { Injectable, NestMiddleware } from "@nestjs/common";
import { Request, Response, NextFunction } from "express";
import * as express from "express";
@Injectable()
export class RawBodyMiddleware implements NestMiddleware {
constructor() {
console.log("RawBodyMiddleware initialized");
}
use(req: Request, res: Response, next: NextFunction) {
// Check if the route matches the desired pattern
if (req.baseUrl.startsWith("/api/auth")) {
// Skip JSON and URL-encoded body parsing for these routes
console.log("Skipping body parsing for:", req.baseUrl);
next();
return;
}
// Otherwise, parse the body as usual
express.json()(req, res, (err) => {
if (err) {
next(err); // Pass any errors to the error-handling middleware
return;
}
express.urlencoded({ extended: true })(req, res, next);
});
}
} And then registering it like so export class AppModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(RawBodyMiddleware).forRoutes("*");
}
} This is my auth config import { betterAuth } from "better-auth";
import { prismaAdapter } from "better-auth/adapters/prisma";
import { PrismaClient } from "@prisma/client";
import { openAPI, bearer, admin } from "better-auth/plugins";
const prisma = new PrismaClient();
export const auth = betterAuth({
database: prismaAdapter(prisma, {
provider: "sqlite", // or "mysql", "postgresql", ...etc
}),
// @ts-expect-error - TS shinanigans
plugins: [openAPI(), admin(), bearer()],
emailAndPassword: {
enabled: true,
autoSignIn: true,
},
}); This is the controller @Controller()
export class AppController {
@All("api/auth/*")
async auth(@Req() req: Request, @Res() res: Response) {
return toNodeHandler(auth)(req, res);
}
} In the main.ts file, disable global bodyParser async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule, {
bodyParser: false,
});
app.enableCors();
await app.listen(process.env.PORT);
Logger.log(`Server running on ${process.env.PUBLIC_URL}`, "Bootstrap");
Logger.log(`Better Auth API Spec on: ${process.env.PUBLIC_URL}/api/auth/reference`, "Bootstrap");
}
bootstrap(); I will report back if i run into any trouble. For now most things work. I did notice that the The TS Errors[5:39:15 AM] Starting compilation in watch mode...
src/lib/auth.ts:11:24 - error TS2322: Type '{ id: "admin"; init(ctx: AuthContext): { options: { databaseHooks: { user: { create: { before(user: { id: string; email: string; emailVerified: boolean; name: string; createdAt: Date; updatedAt: Date; image?: string; }): Promise<...>; }; }; session: { ...; }; }; }; }; hooks: { ...; }; endpoints: { ...; }; $ERROR_COD...' is not assignable to type 'BetterAuthPlugin'.
The types of 'hooks.after' are incompatible between these types.
Type '{ matcher(context: HookEndpointContext<{ returned: Response | Record<string, any> | APIError; endpoint: Endpoint; }>): boolean; handler: Endpoint<...>; }[]' is not assignable to type '{ matcher: (context: HookEndpointContext<{ returned: Response | Record<string, any> | APIError; endpoint: Endpoint; }>) => boolean; handler: HookAfterHandler; }[]'.
Type '{ matcher(context: HookEndpointContext<{ returned: Response | Record<string, any> | APIError; endpoint: Endpoint; }>): boolean; handler: Endpoint<...>; }' is not assignable to type '{ matcher: (context: HookEndpointContext<{ returned: Response | Record<string, any> | APIError; endpoint: Endpoint; }>) => boolean; handler: HookAfterHandler; }'.
Types of property 'handler' are incompatible.
Type 'Endpoint<Handler<string, EndpointOptions, { response: { body: any; status: number; statusText: string; headers: Record<string, string>; }; body: SessionWithImpersonatedBy[]; _flag: "json"; }>, EndpointOptions>' is not assignable to type 'HookAfterHandler'.
Types of parameters 'ctx' and 'context' are incompatible.
Type 'HookEndpointContext<{}>' is not assignable to type '{ body: { [x: string]: any; }; params?: Record<string, string>; method?: "GET"; headers: Headers; request: Request; query: any; _flag?: "json" | "router" | "default"; ... 10 more ...; responseHeader: Headers; } | ... 26 more ... | { ...; }'.
Type 'HookEndpointContext<{}>' is not assignable to type '{ body: { [x: string]: any; }; params?: Record<string, string>; method: Method; headers?: Headers; request?: Request; query: any; _flag?: "json" | "router" | "default"; ... 10 more ...; responseHeader: Headers; }'.
Property 'method' is optional in type 'HookEndpointContext<{}>' but required in type '{ body: { [x: string]: any; }; params?: Record<string, string>; method: Method; headers?: Headers; request?: Request; query: any; _flag?: "json" | "router" | "default"; ... 10 more ...; responseHeader: Headers; }'.
11 plugins: [openAPI(), admin(), bearer()],
~~~~~~~
src/lib/auth.ts:11:33 - error TS2322: Type '{ id: "bearer"; hooks: { before: { matcher(context: HookEndpointContext): boolean; handler: (c: HookEndpointContext) => Promise<{ context: HookEndpointContext; }>; }[]; after: { ...; }[]; }; }' is not assignable to type 'BetterAuthPlugin'.
The types of 'hooks.after' are incompatible between these types.
Type '{ matcher(context: HookEndpointContext<{ returned: Response | Record<string, any> | APIError; endpoint: Endpoint; }>): boolean; handler: Endpoint<...>; }[]' is not assignable to type '{ matcher: (context: HookEndpointContext<{ returned: Response | Record<string, any> | APIError; endpoint: Endpoint; }>) => boolean; handler: HookAfterHandler; }[]'.
Type '{ matcher(context: HookEndpointContext<{ returned: Response | Record<string, any> | APIError; endpoint: Endpoint; }>): boolean; handler: Endpoint<...>; }' is not assignable to type '{ matcher: (context: HookEndpointContext<{ returned: Response | Record<string, any> | APIError; endpoint: Endpoint; }>) => boolean; handler: HookAfterHandler; }'.
Types of property 'handler' are incompatible.
Type 'Endpoint<Handler<string, EndpointOptions, { responseHeader: Headers; }>, EndpointOptions>' is not assignable to type 'HookAfterHandler'.
Types of parameters 'ctx' and 'context' are incompatible.
Type 'HookEndpointContext<{}>' is not assignable to type '{ body: { [x: string]: any; }; params?: Record<string, string>; method?: "GET"; headers: Headers; request: Request; query: any; _flag?: "json" | "router" | "default"; ... 10 more ...; responseHeader: Headers; } | ... 26 more ... | { ...; }'.
Type 'HookEndpointContext<{}>' is not assignable to type '{ body: { [x: string]: any; }; params?: Record<string, string>; method: Method; headers?: Headers; request?: Request; query: any; _flag?: "json" | "router" | "default"; ... 10 more ...; responseHeader: Headers; }'.
Property 'method' is optional in type 'HookEndpointContext<{}>' but required in type '{ body: { [x: string]: any; }; params?: Record<string, string>; method: Method; headers?: Headers; request?: Request; query: any; _flag?: "json" | "router" | "default"; ... 10 more ...; responseHeader: Headers; }'.
11 plugins: [openAPI(), admin(), bearer()],
~~~~~~~~
[5:39:23 AM] Found 2 errors. Watching for file changes. |
Anyone else here able to test out nest? |
@BayBreezy I have tried your solution with the RawBodyMiddleware and it is working great. I have gone a step further and put the creation of the auth object into a service, like that I can use the nestjs DI (to inject my database for example). I didn't found a simple type from better auth to type the "auth" object, so I have left The module import { Module } from "@nestjs/common";
import { DatabaseModule } from "src/services/database/database.module";
import { DatabaseService } from "src/services/database/database.service";
import { AuthController } from "./auth.controller";
import { AuthService } from "./auth.service";
import { ConfigModule, ConfigService } from "@nestjs/config";
const authServiceProvider = {
provide: AuthService,
useFactory: (
databaseService: DatabaseService,
configService: ConfigService
) => {
const baseURL = configService.get("BETTER_AUTH_URL");
const secret = configService.get("BETTER_AUTH_SECRET");
const googleClientId = configService.get("GOOGLE_CLIENT_ID");
const googleClientSecret = configService.get("GOOGLE_CLIENT_SECRET");
return new AuthService(
databaseService,
googleClientId,
googleClientSecret,
baseURL,
secret
);
},
inject: [DatabaseService, ConfigService],
};
@Module({
imports: [DatabaseModule, ConfigModule],
providers: [authServiceProvider],
controllers: [AuthController],
})
export class AuthModule {} The service import { Injectable } from "@nestjs/common";
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { toNodeHandler } from "better-auth/node";
import { openAPI } from "better-auth/plugins";
import { Request, Response } from "express";
import { DatabaseService } from "src/services/database/database.service";
@Injectable()
export class AuthService {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private auth: any;
constructor(
private readonly database: DatabaseService,
private readonly googleClientId: string,
private readonly googleClientSecret: string,
private readonly baseURL: string,
private readonly secret: string
) {
this.auth = betterAuth({
secret: this.secret,
baseURL: this.baseURL,
database: drizzleAdapter(this.database.getDatabase(), {
provider: "pg",
usePlural: true,
}),
emailAndPassword: {
enabled: true,
autoSignIn: true,
},
socialProviders: {
google: {
clientId: this.googleClientId,
clientSecret: this.googleClientSecret,
},
},
plugins: [openAPI()],
});
}
async handler(req: Request, res: Response): Promise<void> {
return toNodeHandler(this.auth)(req, res);
}
} The controller import { All, Controller, Req, Res } from "@nestjs/common";
import { ApiOperation, ApiTags } from "@nestjs/swagger";
import type { Request, Response } from "express";
import { AuthService } from "./auth.service";
@ApiTags("auth")
@Controller()
export class AuthController {
constructor(private readonly authService: AuthService) {}
@All("api/auth/*")
@ApiOperation({
summary: "Auth handler",
})
async auth(@Req() req: Request, @Res() res: Response) {
return this.authService.handler(req, res);
}
} And I have used your example for the app.module and the middleware. |
Ty @Oupsla ! Yes, I struggled with the I was thinking about duplicating the auth config - create a service for Nest DI & one for the better-auth cli. Thoughts? |
Yes
Yes I think you should have to use the Nest DI, because if not you will also have to duplicate every services that you use in your auth config (db service for example, but also services used by this one, etc...) Maybe a good solution will be to do the same thing that drizzle, having a drizzle.config.ts at the root of your project for the CLI, that will contains info about tables configurations, etc... Maybe this will require some changes in the current state of better-auth lib |
you can use private auth: ReturnType<typeof betterAuth>; |
Hey @Bekacru is a nestjs integration not wanted? i wouldn't mind contribuing an integration for nestjs import { Module } from '@nestjs/common';
import { AuthModule } from 'better-auth/nestjs';
@Module({
imports: [
AuthModule.forRoot({ // everything needed for auth!
emailAndPassword: {
enabled: true,
},
}),
],
})
export class AppModule {} I might also include decorators for hooks: import { Injectable } from "@nestjs/common";
import { BeforeHook, AuthContext } from "better-auth/nest";
import { SignUpService } from "./sign-up.service";
@Injectable()
export class SignUpHook {
constructor(private readonly signUpService: SignUpService) {}
@BeforeHook('/sign-up/email')
async handle(ctx: AuthContext) {
// custom logic like enforcing email domain registration
// might throw APIError if something is wrong
await this.signUpService.execute(ctx);
}
} And also, I'm figuring out an easy way to integrate adapters and DI (for example a PrismaService and prismaAdapter) but I might consider just using a nestjs factory instead... if anyone from the community has a better idea lmk! |
Hey guys, Just reporting back with an example app I created with NestJS. the code can be found here: https://github.com/BayBreezy/LearnReact/tree/main/login-system/api I am learning React now(I think they have more jobs than Vue/Nuxt). Here is the frontend to test it out: https://login-system.learn-react.behonbaker.com/ Feedback appreciated 🙏🏽 P.S: @Oupsla I did not go with the approach you mentioned(injecting auth as a provider). Thanks @Bekacru for updating the types. Not getting the same errors with the plugins anymore. |
This is feature request to add nest js support
The text was updated successfully, but these errors were encountered: