#Nestjs Standalone mode

87 messages ยท Page 1 of 1 (latest)

tidal carbon
#

Hi, since Angular introduced standalone components, the NgModules are optional. maybe Nest considering going the same path? allow app to be generated with standalone controllers without the need for AppModule ? I did not think of it all the way, so it might be impossible, but i would love a simpler version of nest without the need to learn about modules.

brave raven
#

As someone who hasn't used Angular since v6, what would be the draw of controllers without registering them to a module? How does that work?

hushed fiber
#

How about providers? Providers in Angular is "global" in the given injector context (you can create new one by lazy loading modules or by passing providers to the component metadata), so standalone components has sense in this way that they "see" this global injector - in NestJS it can creates a lot of complication that every provider used in controller needs to be defined as well in controllers metadata. More boilerplate than using modules approach imho. Also for services which consume some metadata of another services (a lot of custom modules in Open Source) to make some logic need still module system.

gritty dome
#

Well, for angular v15+ you can import any standalone component, pipes, and directive within any class of the same type. So about nestjs if you had a module for one controller, and that you imported some modules within others to register controllers on it, you could probably do it the same way as in angular, something like ```ts
@Controller({
path: 'somePath',
standalone: true,
imports: [AnotherController, AnyModule]
})
export class MyController {}


About the root module, the one that was used in main.ts, angular now made a new bootstrap function that take a standalone component as the first argument and an object with `providers` for any global configuration provided in the whole app like the routing, providing the http client with maybe some interceptors etc...

It looks like this ```ts
bootstrapApplication(AppComponent, {
    providers: [
        provideRouter([dashboardRoute, omniboxRoute, materielRoute], withHashLocation(), withComponentInputBinding()),
        provideHttpClient(),
        provideAnimations(),
        importProvidersFrom(CorailCoreModule.forRoot()),
        importProvidersFrom(TranslateModule.forRoot({
            loader: {
                provide: TranslateLoader,
                useFactory: (http: HttpClient): TranslateHttpLoader => new TranslateHttpLoader(http, './assets/i18n/', '.json'),
                deps: [HttpClient],
            },
        })),
    ]
}).catch((err) => console.error(err));```
#

Like for the par on standalone controllers, I don't know if it could be applied to nestjs

#

Btw importProvidersFrom is to provide legacy modules that do not have provideXXX fn yet

brave raven
#

Maybe it's just because I'm used to Nest's current way of doing it, but that looks like a bit of a mess. Maybe it's just me though.

As for controllers, generally you don't inject other controllers into them, just providers (read: services) and the providers that get injected into them come from the module where the controller is registered.

gritty dome
#

Mmh, we have some legacy code, but it's actually easier!
A more classic one would look like this:

bootstrapApplication(AppComponent, {
    providers: [
        provideRouter(routes),
        provideHttpClient(),
    ]
}).catch((err) => console.error(err));```
brave raven
#

Okay. So how often in a Nest application do you have this single controller approach? Assuming that that's a comprable idea to this angular app

gritty dome
#

And for example, if you need interceptors you would simply have:

bootstrapApplication(AppComponent, {
    providers: [
        provideRouter(routes),
        provideHttpClient(withInterceptors([anInterceptor, anotherInterceptor])),
    ]
}).catch((err) => console.error(err));
gritty dome
#

Anywhere else you would use ts @Controller({ path: 'somePath', standalone: true, providers: [MyService] }) export class MyController {}

brave raven
#

I think my point is this: In an Angular application, you have this single component that holds all the other compoents right? That's how the DOM works, it's a tree of compoenets with an eventual parent. That's not the case with a NestJS application. We have a module that is a container for all the other modules, but there's no one controller that delegates out to all the other controllers.

gritty dome
#

Okay but are the other modules really usefull?

Could we consider something like this? ```ts
@Injectable()
export class MyController {}

@Controller({
path: 'somePath',
standalone: true,
import: [SomeNeededImport],
providers: [MyService]
})
export class MyController {}

bootstrapApplication(async () => { // maybe something simplified here
const app = await NestFactory.create(AppModule);
const globalPrefix = 'api';
app.setGlobalPrefix(globalPrefix);
const port = process.env.PORT || 3000;
await app.listen(port);
Logger.log(๐Ÿš€ Application is running on: http://localhost:${port}/${globalPrefix});
}, {
controllers: [MyController],
providers: [
provideDatabase(withTypeOrm({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'root',
database: 'test',
entities: [],
synchronize: true,
}))
]
});```

gritty dome
brave raven
gritty dome
#

Then you could do something like this ts bootstrapApplication(async () => { ... }, { controllers: [BooksController, AuthorsController, CommentsController, PostsController], ... });

obtuse pier
#

I am on the side of @brave raven. If you use nestjs the intended way, you don't just use a single controller to do all the work. There are services, entities, etc. tied to the controller. Where should we store these with the standalone controllers? I don't see the benefit using this approach. On the side of angular the other hand I find it quite useful, because sometimes you don't need services or other things for a component. But If you just need one controller, without much work around it, then just use a simple express server without using nestjs.

brave raven
# gritty dome Then you could do something like this ```ts bootstrapApplication(async () => { ....

Okay, so now instead of having a container module we've delegated that configuration to the main.ts. What happens to the APP_INTERCEPTOR,APP_GUARD,APP_PIPE, and APP_FILTER providers that are used for globally binding enhancers via DI? Are those also set in the main.ts? I find the app.module to be super useful because we can re-use it in e2e tests and get the exact same enhancer setup as our actual app, rather than needing to remember to call app.useGlobal*().

#

I actually just did this in an Nx monorepo where I made a @project/server/root package that exports a root module so the server and server-e2e projects can import the same root with the same configuration and run it as necessary

gritty dome
# brave raven Okay, so now instead of having a container module we've delegated that configura...

I don't know how they work in nestjs but as for angular, I would say it would be in the AppComponent that is the root component, which I don't know by what it would be replaced in nestjs. About e2e I can't answer we have QAs that make automated tests ๐Ÿ˜… All I know is that cypress now introduced component testing that work as localised e2e that works easier now with standalone components

#
import { StepperComponent } from './stepper.component'

it('mounts', () => {
  cy.mount(StepperComponent)
})```
obtuse pier
brave raven
brave raven
gritty dome
#

And would it be a bad idea to have an AppController that would be a route controller or something like that? (not sure about this one ๐Ÿ˜…)

brave raven
#

How would that work?

#

What would you expeect the "root" controller to look like?

obtuse pier
gritty dome
#

don't know, maybe something like ```ts
@Controller({
root: true,
imports: [BooksController, AuthorsController, CommentsController, PostsController],
providers: [{ provider: APP_INTERCEPTOR, ... }, ...]
})
export class AppController {
async bootstrap() {
const app = await NestFactory.create(AppModule);
const globalPrefix = 'api';
app.setGlobalPrefix(globalPrefix);
const port = process.env.PORT || 3000;
await app.listen(port);
Logger.log(๐Ÿš€ Application is running on: http://localhost:${port}/${globalPrefix});
}
}

bootstrapApplication(AppController, {
providers: [
provideDatabase(withTypeOrm({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'root',
database: 'test',
entities: [],
synchronize: true,
}))
]
});```

#

and that controller path would be ''

#

It may be a completely terrible idea though haha, I'm not even sure myself since I'm far from a nestjs expert ๐Ÿ˜…

brave raven
#

So, it seems like you want to take the idea of what modules already are, and merge them with a controller class

obtuse pier
#

I mean that kind of looks and works like a module

brave raven
#

That's what this looks like to me

gritty dome
#

maybe replacing the root Module would be a bad idea yeah

#

but maybe we could have only the module and the rest would be standalone controllers

brave raven
#

What's the end goal here? The reason you wouldn't want to use modules?

gritty dome
#

mmh actually if you keep a module it doesn't make sense yeah

obtuse pier
#

I mean you can just use the standard app.module and create controllers, services, etc. and register it in the app module. Then you have the same behaviour. Just don't create other modules

gritty dome
#

yeah

brave raven
gritty dome
#

The only cool thing I would actually see with controllers is to have providers directly on them without the need of making modules for that

obtuse pier
brave raven
#

But the whole point of having the modules is to help define the boundaries of the module. It sets up what the controllers and providers should and shouldn'tt have access to via the DI tree

obtuse pier
gritty dome
#

it'd be a little more confortable

brave raven
#

So it opens up to crossing domain lines and spaghettifying your architecture, got it

gritty dome
#

What do you mean?

brave raven
#

The idea behind modules is to have mostly self contained libraries within your application. You have clear domain lines set up via the providers available to the module by defining the imports and the providers (plus global but those will remain either way). If we remove the idea of using a module and just allow any provider from anywhere to be injected (as I understand this would allow) then there's nothing to make a developer stop and think about the reason the post controller needs to use the user service (hint: it probablly doesn't), but because there's no modules to define the providers available at the module level, the user service could just be defined as a necessity and injected.

I guarantee that this would lead to more circular imports than already happen and more support tickets about why the application doesn't quite work as expected

gritty dome
#

But isn't it already possible?

What if I have a UserService I would use that way

@Injectable()
export class UserService { ... }

@Module({
  controllers: [SomeController],
  providers: [UserService],
  exports: [UserService]
})
export class SomeModule {}

@Module({
  controllers: [SomeOtherController],
  providers: [UserService],
  exports: [UserService]
})
export class SomeOtherModule {}```
obtuse pier
#

The whole purpose of modules is making the code more modular (lol3D), easier to understand the dependencies and to organize the code. I think your suggestion is the complete opposite of nestjs philosophy. You have everywhere imports of the same service, where with modules you have a centralized way to define what exports what and which module imports what (More overview of the whole di tree). Also you flood your controller file with unecessary lines of code that can easily be put into another file (module). Thats the same if you put your html code of an angular component into the components .ts file. It just makes the file bigger and harder to find things

gritty dome
#

How would that be worse than ```ts
@Injectable()
export class UserService { ... }

@Controller({
path: 'somePath',
standalone: true,
providers: [UserService]
})
export class SomeController {}

@Controller({
path: 'someOtherPath',
standalone: true,
providers: [UserService]
})
export class SomeOtherController {}```

obtuse pier
brave raven
#

And yes, it is already possible, but I feel there's a greater impact by having to think in terms of modules rather than just adding a new provider the this supposed @Controller()'s providers property

gritty dome
brave raven
#

That's the other big thing as well, Nest has providers singleton by default by modules. That means if a module is in the imports, the providers in the exports of that module are singleton and re-usable, but if you add the providers to the new module's providers array, then Nest will create new instances.

Is there a way that could/would be handled with this proposed @Controller()?

gritty dome
gritty dome
obtuse pier
brave raven
#

That's why I initally asked for a reason to go for this standalone component approach, because I'm not familiar with it

gritty dome
brave raven
#

Nest just doesn't have the concept of providedIn to begin with

gritty dome
brave raven
gritty dome
gritty dome
obtuse pier
#

I am using the latest angular version in my job and I think standalone components are good for angular, but I don't see the need in nestjs for it

gritty dome
#

Almost @Injectable({providedIn: 'root'}) everyWhere

obtuse pier
brave raven
brave raven
gritty dome
gritty dome
gritty dome
obtuse pier
obtuse pier
gritty dome
gritty dome
obtuse pier
gritty dome
obtuse pier