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?
30 Replies
im thinking of something like this. but typescript does not seem to like this
im looking for the right return type for the RouterWithInjectedServices type
im getting the error when i add this object to the router.
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 π
thats true. but that would mean i need to create a middleware for eacht routegroup
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
do a base procedure per router group that has the services you want
or you can create each router group in a callback function
potentially the latter can be done with a DI library like tsyringe
curious what you decide on being nicest, keep us in the loop!
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.
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
thank you @Captain!
keep us in the loop of how you solve this, DI is always an interesting problem
Unknown Userβ’2y ago
Message Not Public
Sign In & Join Server To View
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
Unknown Userβ’2y ago
Message Not Public
Sign In & Join Server To View
ahso. thats what im doing too. i moved all util to the nextjs app and the rest as a node lib in NX workspace
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
Usage
Some usage details could be fairly easily refactored towards a nicer experience. I think something like
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.
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
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:
Then our router is just like:
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.
How do you share the schema with the front-end?
Anything wrong with having a separate package with the schemas and importing from there?
Nope, lgtm
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...
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?yeah, would need to do
toRouter(myService).createCaller()
cool yeah I don't think that's a problem just curious
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 sent the code example to them already they liked it π
and yeah i definitely see this making things easier for our backend guys
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
@alex / KATT hmm yeah we pass dependencies to our controllers via the class constructor. With this method we could do something like this?
clone it and have a play!