I'm using NestJs in a monorepo setup using Nx, with NextJs as a client. My basic architecture is having modules that contain all the logic (client, server and api code) for a specific domain (auth, users, posts etc), and then having services that these modules consume (CssService, DbService, JwtService, ValidationService etc). The folder structure looks something like this (I've removed unnecessary barrel/Nx lib files for this demo):
modules
auth
api
signup
client
service-methods-and-properties
signup.error-messages.ts
signup.fields.ts
signup.form.logic.ts
signup.form.ui.tsx
signup.form.tsx
signup.mutation.generated.ts
signup.mutation.gql
signup.service.ts
signup.types.ts
server
service-methods-and-properties
signup.input.ts
signup.logic.ts
signup.response.ts
signup.service.ts
signup.types.ts
client
pages
signup
signup.page.tsx
server
auth.module.ts
auth.resolver.ts
services
db
client
db.service
server
db.service
In my NestJs auth resolver (modules/auth/server/auth.resolver), I import the SignupServerService to be used by the signup handler:
import { SignupServerService } from '../api/signup/server';
@Resolver()
export class AuthResolver {
constructor(private readonly signupServerService: SignupServerService) {}
@Mutation(() => SignupServerService.Response)
async signup(
@Args('input') input: SignupServerService.Input
): Promise<typeof SignupServerService.Response.prototype> {
return this.signupServerService.logic(input);
}
}
Here is the SignupServerService:
@Injectable()
export class SignupServerService {
constructor(
private readonly gqlServerService: GqlServerService,
protected readonly dbServerService: DbServerService,
protected readonly jwtServerService: JwtServerService
) {}
static readonly Input = SignupInput;
static readonly Response = SignupResponse;
readonly logic = signupLogic;
}
However, there are a couple of problems with this, and some related questions I have:
The code works, but I get a couple of TS errors for this line in my resolver
@Args('input') input: SignupServerService.Input, which disappears if I useSignupInputdirectly from thesignup.input.tsfile, rather than accessing it statically via theSignupServerServiceclass. The errors are'SignupServerService' only refers to a type, but is being used as a namespace here.ts(2702)andParameter 'input' of public method from exported class has or is using private name 'SignupServerService'.ts(4073)Piggy backing on problem 1, I'm using some static properties on the
SignupServerServiceso I can passSignupServerService.Responseto the@Mutationdecorator args, because the following doesn't work@Mutation(() => this.signupServerService.Response). I'm wondering why this doesn't work? And is my pattern of using static methods to pass properties to the decorator args the best way of doing this? Should I be using TS namespaces instead of static class methods or is there a better way of handling this?Furthermore, I've heard that static methods cause issues with unit testing and should be avoided? This is mentioned in the logging section of the NestJs docs, but it isn't clear enough to draw any conclusions: "This technique, while simple, doesn't utilize dependency injection for the MyLogger class. This can pose some challenges, particularly for testing, and limit the reusability of MyLogger"
This leads me to a more general question about using NestJs in a monorepo. Frequently I need to share a class/service (for example my custom JwtService - note, this is not NestJs's built in JwtService) between some NestJs code and some React code. Naturally, functional react components don't use a DI system, so to access properties on the JwtService I need to either make them static (e.g.
JwtService.ACCESS_TOKEN_COOKIE_NAME) which means duplicating the property in the JwtService class (e.g.static ACCESS_TOKEN_COOKIE_NAME = ACCESS_TOKEN_COOKIE_NAME; ACCESS_TOKEN_COOKIE_NAME = ACCESS_TOKEN_COOKIE_NAME), or, don't use static methods and instantiate the service manually in my react code (e.g.const { ACCESS_TOKEN_COOKIE_NAME } = new JwtService()). However, this becomes unusable if the service has dependencies which I have to manually provide to the class constructor argument, particularly if the dependecies are only used by methods used in the NestJs code (e.g.const { ACCESS_TOKEN_COOKIE_NAME } = new JwtService(new SomeDependencyNotNeededForReact())). So what is the best solution in this scenario?