#Need advice on separating admin and user API endpoints for all modules

44 messages Β· Page 1 of 1 (latest)

devout pelican
#

Hey, my server has two endpoints, one user facing endpoints and other admin facing endpoints and I have modules like auth, user, services, etc,. My current structure is following:

src/modules/
β”œβ”€β”€ auth
β”‚Β Β  β”œβ”€β”€ auth.controller.ts
β”‚Β Β  β”œβ”€β”€ auth.module.ts
β”‚Β Β  β”œβ”€β”€ auth.service.ts
β”‚Β Β  └── dto
β”‚Β Β      β”œβ”€β”€ user-login.dto.ts
β”‚Β Β      └── user-register.dto.ts
β”œβ”€β”€ service
β”‚Β Β  β”œβ”€β”€ dto
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ create-service.dto.ts
β”‚Β Β  β”‚Β Β  └── update-service.dto.ts
β”‚Β Β  β”œβ”€β”€ service.controller.spec.ts
β”‚Β Β  β”œβ”€β”€ service.controller.ts
β”‚Β Β  β”œβ”€β”€ service.module.ts
β”‚Β Β  β”œβ”€β”€ service.service.spec.ts
β”‚Β Β  └── service.service.ts
└── user
    β”œβ”€β”€ dto
    β”‚Β Β  └── update-user.dto.ts
    β”œβ”€β”€ user.controller.ts
    β”œβ”€β”€ user.module.ts
    └── user.service.ts

Each module will have user and admin API. I need advice on how do I structure my project, so that I can easily manage admin and user endpoint for each module.
I read somewhere that I can create multiple controllers and services within a module to separate logic of user and admin APIs, as follows:

β”œβ”€β”€ service
β”‚Β Β  β”œβ”€β”€ service.controller.ts
β”‚Β Β  β”œβ”€β”€ service-admin.controller.ts
β”‚Β Β  β”œβ”€β”€ service.module.ts
β”‚Β Β  └── service.service.ts
β”‚Β Β  └── service-admin.service.ts

But I am not sure if this is the correct way or not.

kind sky
#

"admin" and "user" are roles, right? And as roles, they really shouldn't be found in file naming. πŸ™‚ You need to abstract higher. When you do, you can have a "core" or "common" module as a library and "auth" and "user" are part of that library. Your data should then control access and who is who, not your code. Hope that makes sense. πŸ™‚

devout pelican
#

So what I want is to add an alias admin in Admin APIs and the reason behind creating separate file is to manage admin endpoints easily otherwise single controller/service file will have lots of lines code for logic for admin and user

#

I hope I explained it correctly πŸ˜„

kind sky
#

Yes, you can even have different admin and user "apps" to create the two end points, if you wanted. My point is, the authentication, authorization and the user's themselves are all the same functionality for both apps, right? If not, you should abstract your logic to a point that these modules can be used universally (like in a library) and only the data involved would cause the logical outcomes to differ (like a non-admin user tries to log into the admin app, gets a "no, sorry. You aren't allowed"). When you can abstract like this is when Nest's beauty really shines.

#

I'll add to this, you can and should create files only for admin functionality. But, it is dealing with processes at that point. Something only admins would do for instance, would be to change roles of internal users. For instance, say you have a blog app with authors, editors and readers. Only an admin would be able to change a reader to an editor. That process would be a purely administrative task, so it would be found in your admin path or app.

#

One could theoretically get that also abstracted into a general CRUD activity and regulate it with permissions, but if you really want the logical break between admin and user paths, you'd have to do the separated logic too.

One thing I've worked with before is using a general CRUD logic and "extending" it with such business logic during triggers. For instance, if a blog role edit is made, check it is an admin in a pre-save trigger for the blogger roles. But, that kind of abstraction is really advanced. πŸ™‚

devout pelican
# kind sky Yes, you can even have different admin and user "apps" to create the two end poi...

My point is, the authentication, authorization and the user's themselves are all the same functionality for both apps, right?
If you're asking that users are the same and differentiate by roles then no. Users and Admins are different and will have different roles as well, so I will have to create different auth and role guards for both of them because logic is different between both.

kind sky
#

So, an admin isn't a user?

devout pelican
kind sky
#

I'd highly suggest not using guards for authorization per controller or resolver. It means for every change of a role's abilities (granted this doesn't happen too often, but still), you'll have to change code and to me that is a no-go.

devout pelican
kind sky
#

An admin is a user. πŸ™‚ I think you need to change the term user to non-admins or to something that describes your non-admins. Clients. Customers, etc.

#

Who are all also users too.

#

So again, as I said, users, authentication and authorization are generic or should be. Only the data involved should cause the routing logic to change. Or, if you do admin.my-site.com, you'd need a different app, but using those same generic features (in a library). When a user logs in, then the system knows, the user is either a client or an admin and your authorization logic kicks in and says, "you can or can't do this", which even includes the ability to log in, or not.

#

Think about this one too. Guests are users too. Just not registered users. πŸ™‚ You need paths for them too, right? And allowing all this to be dictated via decorators will end up being a nightmare.

#

For a small-ish app, you can run with guards for authorization, but as soon as you start speaking about admins and non-admins having different paths to get things done, you need a more robust and abstracted system. This is why Nest suggests CASL for ABAC as a solution, although it too ends up in guards in the example, which I don't like.

#

It is much too simplified.

#

For the longest time I've been thinking about writing an article on the subject of more advanced authorization, because I believe this is one subject where so many people get stuck. I just haven't found enough motivation to do it.

devout pelican
devout pelican
#

But this has introduced a new challenge for me because till now I was developing APIs that will be used by authenticated users/customers and guests/public but now the auth guard will also apply to admin endpoints also if I choose to go with separate controllers/services approach.

#

The solution I'm thinking to apply is different module for both user/customer and admin, where user module will have global Auth Guard with exception @Public decorator and admin module will have global Role Guard with some exceptions. is this possible though?

#

our application is somewhat simple in this case. Users will use user APIs and admins will have different roles and use the appropriate APIs.

#

This is my whole server folder structure, for you to get an idea

src/
β”œβ”€β”€ app.controller.ts
β”œβ”€β”€ app.module.ts
β”œβ”€β”€ app.service.ts
β”œβ”€β”€ common
β”‚Β Β  β”œβ”€β”€ decorators
β”‚Β Β  β”œβ”€β”€ guards
β”‚Β Β  └── interfaces
β”œβ”€β”€ database
β”‚Β Β  β”œβ”€β”€ data-source.ts
β”‚Β Β  β”œβ”€β”€ entities
β”‚Β Β  β”œβ”€β”€ enums
β”‚Β Β  β”œβ”€β”€ factories
β”‚Β Β  β”œβ”€β”€ migrations
β”‚Β Β  └── seeders
β”œβ”€β”€ main.ts
β”œβ”€β”€ modules
β”‚Β Β  β”œβ”€β”€ auth
β”‚Β Β  β”œβ”€β”€ service
β”‚Β Β  └── user
└── types
    └── express

users/customers and admins will interact with each module.

kind sky
#

What you should get from that article for sure is the "feature" and the "mini-onion" concepts of Nest modules. I say this because, your project folder structure will break Nest's intended way of doing things.

devout pelican
devout pelican
# kind sky Ok. Before we continue, please read this article. πŸ™‚ https://dev.to/smolinari/ne...

Great article Scott. There are some things I learned and will implement to my server such as Nested modules (Blog module -> Draft Module, Comment Module) and place entiites per modules to avoid placing entities in same place.

Still I'm confused about one thing, prefix admin routes by /admin in each module πŸ˜…. Sorry but I have worked with Express.js the most and it is easy to differentiate routes in express because it is not following module approach or any approach at all. an example would be following:

app.use('/api/admin', AdminRoutes)
app.use('/api', UserRoutes)

As I'm new to Nest.js and its module approach, this is quite complicating for me! I cannot figure out how do I separate routes prefixed by /admin for each module and its logic.

Also I'm using guards and decorators, so some global level guards of user routes also applies on admin routes, so I would like to know the recommended way to create guards that only applies to user paths globally and ignores the admin paths, and guards that only applies to paths prefixed byadmin.

#

Sharing a simple example of services controller:

@Controller('services')
export class ServicesController {
  constructor(private readonly servicesService: ServicesService) {}

  /* User Paths - Will have a global JWT guard */
  @Get()
  async findAll() {
    return this.servicesService.findAll();
  }

  @Get()
  async findOne() {
    return this.servicesService.findOne();
  }

  /* Admin Paths - Will have a global JWT guard, should be prefixed by `admin` and will also have global role guard for only admin routes */
  @Get(':id')
  async findOne(@Param('id') id: string) {
    const service = await this.servicesService.findOne(id);
    if (!service) {
      throw new HttpException('Service not found', HttpStatus.NOT_FOUND);
    }
    return service;
  }

  @Post()
  async create(@Body() createServiceDto: CreateServiceDto) {
    return this.servicesService.create(createServiceDto);
  }

  @Put(':id')
  async update(@Param('id') id: string, @Body() updateServiceDto: UpdateServiceDto) {
    return this.servicesService.update(id, updateServiceDto);
  }

  @Delete(':id')
  async remove(@Param('id') id: string) {
    return this.servicesService.remove(id);
  }
}
kind sky
#

@devout pelican - I'll have to be honest, I'm not the crack when it comes to REST with Nest. I've used Nest exclusively with GraphQL and only used Rest sparingly (like for auth). But, I'll still try to help. First thing, did you see Nest's own router module?

devout pelican
kind sky
#

I'd highly suggest keeping guards that aren't actually globally used to be constrained only to the module for the controllers it will guard. Don't try to mix and match guards across modules. If the guards can be used globally, the guards would be put in the authorization module, as it is cross cutting and that authorization module should be in a "common" or "core" module, which contains modules that can be used across the whole application (and theoretically be a library for future apps).

kind sky
devout pelican
#

but I have to define separate modules to use this right?

|- modules
  |- app
    |- app.module.ts
    |- module1
    |- module2
    |- module3
  |- admin
    |- admin.module.ts
    |- module1
    |- module2
    |- module3

Is this recommended?

#

Sorry I'm asking a lot questions related to project structure, but I am really stuck at this for ~2 days and still can not figured out how this can be done properly.

#

I'm at the initial level of my project so it will be good for me to figure out a good way of handling this instead of working with mess later.

kind sky
#

No problem with questions. If you intend on putting both admin and "user" functionality in the same app, I'd put the app.module.ts at the top, but have two folders with modules below it. So,

    |- app.module.ts
      |- admin
        |- admin.module.ts
        |- sub-admin-module1
        |- sub-admin-module2
        |- sub-admin-module3
      |- feature1
          |- feature1.module.ts
          |- sub-feature1-module1
          |- sub-feature1-module2
          |- sub-feature1-module3 
      |- feature2
          |- feature2.module.ts
          |- sub-feature2-module1
          |- sub-feature2-module2
          |- sub-feature2-module3 
      |- core
          |- core.module.ts
          |- sub-core-module1
          |- sub-core-module2
          |- sub-core-module3 

Although I added "sub" in the modules in the second level, that isn't what you'd name them. πŸ™‚

Also, if "admin" is going to be a lot of features on its own, I'd also go with feature folders as modules and then sub-modules, to build out the feature.

devout pelican
#

Also, if "admin" is going to be a lot of features on its own, I'd also go with feature folders as modules and then sub-modules, to build out the feature.
Yup, admin will have as much features as "user"/api will have, so if API has 3 modules with sub-modules, the Admin module will also have 3 module with sub-modules, but they will contains /admin endpoints.

#

Thanks a lot Scott for helping out πŸ˜„. Really cleared my doubts related to clean architecture.

pine troutBOT
#

This post has been marked as resolved. βœ…
Please read through the conversation and resolution, if you are having the same issue.
If you were the original author of the post and the issue is still fresh (within a few days) and you are still have having trouble, continue to reply here. If you are not the original author of the post or the post has aged, start a new thread linking this one as relevant to your problem, providing as much additional information as possible.