How to do dependecy injection?

My routes are grouped by using the router object. i'd like to be able to inject a service to each route group. is this possible and how can i do this?
C
Captainβ€’463d ago
im thinking of something like this. but typescript does not seem to like this
type RouterWithInjectedServices<T> = (service: T) => AnyRouter

export const greeting: RouterWithInjectedServices<Greeting> = (service) => router({
hello: procedure.query(() => {

return service.hello()
}),

goodbye: procedure.query(() => {
return service.goodbye()
})

})
type RouterWithInjectedServices<T> = (service: T) => AnyRouter

export const greeting: RouterWithInjectedServices<Greeting> = (service) => router({
hello: procedure.query(() => {

return service.hello()
}),

goodbye: procedure.query(() => {
return service.goodbye()
})

})
im looking for the right return type for the RouterWithInjectedServices type im getting the error when i add this object to the router.
Type 'RouterWithInjectedServices<Greeting>' is not assignable to type 'AnyProcedure | AnyRouter'.
Type 'RouterWithInjectedServices<Greeting>' is missing the following properties from type 'Procedure<"query" | "mutation" | "subscription", any>': _type, _def, _procedure
Type 'RouterWithInjectedServices<Greeting>' is not assignable to type 'AnyProcedure | AnyRouter'.
Type 'RouterWithInjectedServices<Greeting>' is missing the following properties from type 'Procedure<"query" | "mutation" | "subscription", any>': _type, _def, _procedure
N
Nickβ€’463d ago
tRPC has Context for this. You can also extend the Context using Middlewares. This is all well documented so you can have a read up on those pages πŸ™‚
C
Captainβ€’463d ago
thats true. but that would mean i need to create a middleware for eacht routegroup
N
Nickβ€’463d ago
Depending on your DI approach you could also configure Context with a factory(s) function to get things from DI then each procedure can just request what it needs It’s fairly normal to have multiple base procedures though, so you can create a base procedure co-located above the router with the right middlewares composed together to inject your services. It’s no more boilerplatey than composing your service directly on the router
AK
Alex / KATT πŸ±β€’463d ago
do a base procedure per router group that has the services you want or you can create each router group in a callback function
const appRouter = router({
post: createPostRouter({ postService })
})

function createPostRouter(services: { postService: PostService }) {
return router({ /* ... */ })
}
const appRouter = router({
post: createPostRouter({ postService })
})

function createPostRouter(services: { postService: PostService }) {
return router({ /* ... */ })
}
potentially the latter can be done with a DI library like tsyringe curious what you decide on being nicest, keep us in the loop!
C
Captainβ€’462d ago
That sounds exactly like what i am looking for. Thank you both so much! Im going to try tsyringe and see if i can get it to work. I'll definitely post here what i come up with. Thank you both so much for your help and the great library.
C
Captainβ€’462d ago
C
Captainβ€’462d ago
I have learned so much in the past 2 months by just trying to understand trpc and how its connected with each other. Again thank you so much for your support
AK
Alex / KATT πŸ±β€’462d ago
thank you @Captain! keep us in the loop of how you solve this, DI is always an interesting problem
UU
Unknown Userβ€’462d ago
C
Captainβ€’462d ago
im not sure if this would work in my case. im using supabase with row level security and the supabase client is dependent on the req,res object. i have no base clase which export the supabase client. so DI is in the end not the best solution for this. for now im gonna go with service middleware for each route group im not sure what u mean with monorepo standalone package and binding the trp router, could you elaborate? i have a node lib in my case which exports the router
UU
Unknown Userβ€’462d ago
C
Captainβ€’462d ago
ahso. thats what im doing too. i moved all util to the nextjs app and the rest as a node lib in NX workspace
N
Nickβ€’459d ago
Been thinking about this one recently as I am convinced that the code above is needlessly complex. Wrapping procedures/routers in DI factories just seems not great to me. Have had a play with tsyringe and some trpc/typescript magic and have a rough prototype here which seems to work perfectly albeit could be tidied up usage-wise: https://stackblitz.com/edit/github-ewmssd?file=src/server.ts
Trpc Standalone Server Example - StackBlitz
Run official live example code for Trpc Standalone Server, created by Trpc on StackBlitz
N
Nickβ€’459d ago
Usage
N
Nickβ€’459d ago
Some usage details could be fairly easily refactored towards a nicer experience. I think something like
const baseProcedure = /* auth, logging, etc */
const globalInjector = createInjector(container, { foo: Foo, bar: Bar })

// opts.ctx.bar does not get added because it's not requested
const routeGroupProcedure = baseProcedure.use(globalInjector({ foo: true }))

const appRouter = t.router({
thing: routeGroupProcedure.query(async opts => {
return await opts.ctx.foo.doStuff()
}),
otherThing: routeGroupProcedure.query(async opts => {
return await opts.ctx.foo.doOtherStuff()
}),
})
const baseProcedure = /* auth, logging, etc */
const globalInjector = createInjector(container, { foo: Foo, bar: Bar })

// opts.ctx.bar does not get added because it's not requested
const routeGroupProcedure = baseProcedure.use(globalInjector({ foo: true }))

const appRouter = t.router({
thing: routeGroupProcedure.query(async opts => {
return await opts.ctx.foo.doStuff()
}),
otherThing: routeGroupProcedure.query(async opts => {
return await opts.ctx.foo.doOtherStuff()
}),
})
Nice thing about moving this inside a middleware is we could in theory account for niceties of DI, like transient/request/singleton scoping, while only instantiating what's needed for a given request.
C
Captainβ€’458d ago
looks very interesting nick i spent sometime with this too. in the end, i am using Tsyringe to do IoC. one thing i am unable to solve is the need to register a middleware to resolve the service that is needed right now, i have a base class who's params are resolved using useValue. all other classes are register with the container using useClass. i do still need to resolve it in a middleware and bind it to the context
I
isaac_wayβ€’457d ago
This is a really interesting discussion - The way our backend devs have been doing it is entirely separate from tRPC which I find really interesting compared with the approaches in this discussion. It decouples completely from the trpc code (not saying it's better or worse, just very different from the above approaches). Basically we have these separate "service" classes that contain all DB interactions, "Controller" classes that contain the resolvers. Each of these may have private members which are services they're dependent on, which are passed when initializing the services (so you can pass in mocks and such). Allows testing of the controllers and services independently of tRPC. IE:
export class AuthController {
private authService: AuthService
private codeService: CodeService
private emailService: EmailService
private notificationUtil: NotificationUtilities
private smsService: SmsService
private userService: UserService
// ...
export class AuthController {
private authService: AuthService
private codeService: CodeService
private emailService: EmailService
private notificationUtil: NotificationUtilities
private smsService: SmsService
private userService: UserService
// ...
Then our router is just like:
.mutation('loginWithEmailAndPassword', {
input: LoginInputSchema,
output: TokensAndUserOutputSchema,
resolve: authController.login,
})
.mutation('loginWithEmailAndPassword', {
input: LoginInputSchema,
output: TokensAndUserOutputSchema,
resolve: authController.login,
})
AuthController contains the resolvers, and uses the private members to do stuff. This is v9 btw πŸ˜… Not sure if this solves your problem, but it's how we've done dependency injection and it seems really straightforward. I haven't tried doing injection via contexts so IDK how it compares.
AK
Alex / KATT πŸ±β€’457d ago
How do you share the schema with the front-end?
I
isaac_wayβ€’457d ago
Anything wrong with having a separate package with the schemas and importing from there?
AK
Alex / KATT πŸ±β€’457d ago
Nope, lgtm
AK
Alex / KATT πŸ±β€’437d ago
i'd like to get your POV on this: https://github.com/trpc/trpc/pull/3727
GitHub
play with router classes by KATT Β· Pull Request #3727 Β· trpc/trpc
Preface I wouldn't say I like OO, but these classes are neat ways of encapsulating logic & makes it more natural to make private methods and calling stuff between classes etc . What it does...
I
isaac_wayβ€’437d ago
this would be huge - would eliminate a lot of boilerplate we're currently writing and at first glance I don't see any drawback from our current approach. I guess the one change from our current approach is that the trpc methods wouldn't be directly callable, would need to go through createCaller yeah?
AK
Alex / KATT πŸ±β€’437d ago
yeah, would need to do toRouter(myService).createCaller()
I
isaac_wayβ€’437d ago
cool yeah I don't think that's a problem just curious
AK
Alex / KATT πŸ±β€’437d ago
have some peeps on your team have a look at the PR, would love some feedback i'm going to do the same this week, we also use some "service classes" at work and i think it's really annoying to jump between files i hate context switching
I
isaac_wayβ€’437d ago
i sent the code example to them already they liked it πŸ˜„ and yeah i definitely see this making things easier for our backend guys
AK
Alex / KATT πŸ±β€’437d ago
if any of you wanna fork that PR and play around with any of the todos that would be cool too you do some sort of DI i think wanna make sure it works for people's common DI patterns
I
isaac_wayβ€’437d ago
@alex / KATT hmm yeah we pass dependencies to our controllers via the class constructor. With this method we could do something like this?
const myRouter = toRouter(new Controller({someService: new SomeService()}))
const caller = myRouter.createCaller({ctx});
caller.myMethod();
const myRouter = toRouter(new Controller({someService: new SomeService()}))
const caller = myRouter.createCaller({ctx});
caller.myMethod();
AK
Alex / KATT πŸ±β€’437d ago
clone it and have a play!
More Posts
A lot of WHYGreat post from @cje on Twitter but also got a lot of questions 1. If the data flow starts from `How are they defined separately?I am using monorepo for my project design and I want to separate the definition of trpc completely ihow can i get procedure name?is it possible to get procedure name? i would like to append a service to ctx based on procedure namRight way to catch all unexpected errors before they reach the userHi, I was wondering, what's the right way to catch all unexpected errors (like db errors) and replactRPC sockets with reactTrying to make my React app work with socket with minimal server but getting error: "Uncaught TypeEShould useQueries be able to 'select' data?Hi! I'm trying to use the newly implemented `useQueries` method to query for multiple items. I need Right way to structure your code when using tRPC?I was wondering how you properly structure your tRPC ruter code? Writing all the code in the routersReact Native UsageHello, I wanted to know if trpc can be used with react native ? And is it possible on a bare react ntransformers, tensor.js, PyTorch and tRPCdumb question: does anyone has experience with tensorflow.js? is there any major obstacle to use tensubscriptionHow do you guys Authenticate / Authorize Subscription ?NextJS and createProxySSGHelpers context type errorHi guys, do you guys know a better way of clean this typescript error? createProxySSGHelpers({ Validating PermissionsHi! A common operation that I'm doing in tRPC is validating that a person has permissions to perforAny typescript chads able know if it's possible to map a type to another type w genericsNot 100% sure if this is appropriate to ask here but I figured there's a lot of good TS developers ouseQuery enabled not working???Even setting the `enabled` to false --> `trpc.order.get({id: "123"}, {enabled: false})` still make`QueryClientProvider` not included in `withTRPC`?Trying to use `import { useIsMutating } from "react-query"` but it's throwing `Error: No QueryClientHandle React error boundarySeems like Im doing something wrong as I can't handle the error boundary from trpc query. I've queryAny tips for Typescript slowness?We successfully migrated from 9.x to 10 but are still seeing slow VS Code perf (10+ seconds to get aChange status of useMutation from 'success' back to 'idle'Hiya, I have a mutation for creating stuff that can run a few different ways on a page, and I want tIs it possible to call one procedure in another?I have multiple routers in my trpc server. For example userRouter (e.g. procedure: getUser) and post