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.
#Nestjs Standalone mode
87 messages ยท Page 1 of 1 (latest)
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?
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.
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
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.
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));```
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
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));
Only once, it replaces the AppModule
Anywhere else you would use ts @Controller({ path: 'somePath', standalone: true, providers: [MyService] }) export class MyController {}
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.
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,
}))
]
});```
Maybe imports on controllers wouldn't make sense in Nest though
This is only a signle controller right? That might work for a single service based server, but most servers are not single service based, right? We end up having things like /books, /authors, /comments, /posts, etc
Then you could do something like this ts bootstrapApplication(async () => { ... }, { controllers: [BooksController, AuthorsController, CommentsController, PostsController], ... });
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.
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
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)
})```
Don't you lose the modularity of the code also, when you have to provide the APP_INTERCEPTOR, APP_GUARD, etc. in the main file?
Right, but with Nest there';s no "root" component. The closest there is is a root module (usually called AppModule), but because controllers don't import each other, like Angular components do, there's no way to really have that sense of root without the module.
To an extent, yes. In this hypothetical, it would now be up to the main.ts or similar to remember to bind the enhancers rather than letting the modules take care of it. It's kind of a blessing and a curse, as now it's modifiable, but it's also something to have to remember. I already see a lot of people forgetting it when using app.useGlobal*() methods
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 ๐ )
I mean in this scenario, installed libraries wouldn't be able to provide that stuff and you need to manually register them. If I understand his concept right
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 ๐
So, it seems like you want to take the idea of what modules already are, and merge them with a controller class
I mean that kind of looks and works like a module
That's what this looks like to me
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
What's the end goal here? The reason you wouldn't want to use modules?
mmh actually if you keep a module it doesn't make sense yeah
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
yeah
I mean, I wouldn't ever recommend this, but you're right
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
This defeats the whole purpose of modules haha
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
Thats a better idea. But why would you want that?
It's easier to understand for beginers and you won't need exports anymore
it'd be a little more confortable
So it opens up to crossing domain lines and spaghettifying your architecture, got it
What do you mean?
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
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 {}```
The whole purpose of modules is making the code more modular (
), 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
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 {}```
I think that is a bad practice. You only put the service into one module and export it. Then you import the module into the other module that requires the service
This, while possible, is actually a different case as well. By using providers again, you're also creating a new instance of the UserService rather than re-using the one create in the original UserMoudle.
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
Mmh I probably don't see the difference between angular and nestjs modules then, because to me they have the same purpose and angular chose to make the code more modular by letting users import or provide what they trully need and not just a huge set of tools (like CommonModule for example that is now splitted in NgIf, NgFor, AsyncPipe, etc.)
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()?
Yeah, a bad practice but still possible, but wouldn't it be the same with Controllers? A bad practice to avoid?
Yeah the DI works just like angular then but without the providedIn property, is that it?
You don't have overview of the di tree and you would create copies of the same di entry that otherwhisee would be one
Can't say, I haven't touched Angular in years to be honest. Since like v6? It's been a while
That's why I initally asked for a reason to go for this standalone component approach, because I'm not familiar with it
Okay I see, If you wanted to make them reusable then, yes, you would probably need a providedIn that, if I understood, is not recommended at all in nestjs
Nest just doesn't have the concept of providedIn to begin with
Yeah I get that, it'd be one instance of the provider in every controllers
Gods that sounds like a nightmare when it comes to some state management
I know, but is there a reason for that?
Yeah of course haha, which is why we rarelly use it in angular apps ๐
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
Almost @Injectable({providedIn: 'root'}) everyWhere
for that you have ngrx stores. services are in most cases only a facade for the stores
Which is fair, but that's generally not the case with Nest, as you're aware
yes
Design choice by Kamil years ago, before I joined the team, so I can't really say
Don't know if it could be a great thing or not in nestjs, all I know is that there would indeed be some work to make it possible and confortable to use. Was just answering to the very first question of jmcdo on that post haha! It may indeed be a terrible idea, or maybe with some changes one of the best improvements, can't say ๐
Mmh, I wouldn't say that, depends on the size of your app I'd say.
Okay, and do you think it could be a possibility for nest?
Very doubtful
I don't know how its in other projects. Most of our services are just facades for stores. There are other ones that actually have business logic in them, but for the most cases its just facades
I think when you use nest more, you will understand why its a bad idea. But I think it was a good opportunity to talk about such stuff
We actually handle states within services without any store and go for a fully declarative way for updating it with signals and rxjs
Yeah, probably! I'll keep that in mind and indeed it was a good talk ๐
We use stores and the services pull the data out into an observable and work with that. We didn't adapt to signals yet. How is it working with them?
We can maybe open a new thread or forum if you want?
yeah, but its not related to nestjs. Maybe just private message?