macstr1k3r / trpc-nestjs-adapter

An adapter, allowing you to use TRPC inside of Nest.JS
157 stars 14 forks source link

Brainstorm: decorators for routing procedures? #1

Closed KerryRitter closed 1 year ago

KerryRitter commented 1 year ago

I am wondering if there is a way to create custom decorators to route the procedures, i.e.

@Controller('api/cats')
export class CatsController {
  @Mutation({ input: z.object({ name: z.string(), }).optional() })
  createCat(@Input() input: { name: string }) {
    // do stuff
  }

  @Query({ input: z.object({ id: z.string(), }).optional() })
  getCat(@Input() input: { id: string }) {
    // do stuff
  }
}

What's your initial take? I can work on a PoC

macstr1k3r commented 1 year ago

Apologies for the late reply.

I'm not sure this is easy to achieve.

But if you bring over a PoC, I'm more than happy to work on merging it in.

KerryRitter commented 1 year ago

I did have some luck: https://github.com/macstr1k3r/trpc-nestjs-adapter/compare/master...KerryRitter:trpc-nestjs-adapter:decorators?expand=1

@TRPCQuery() works, and I think we can make @TRPCInput() and whatnot work - looks like this might be cool. Only downside is the inferred typing isn't quite as slick, but not too bad to pre-declare the object and use typeof.

macstr1k3r commented 1 year ago

I can see how this will get the server set-up and responding, but how do we achieve what tRPC primarily achieves, which is sharing the types between the client and the server?

KerryRitter commented 1 year ago

What do you need to accomplish this? I can expose the router or tRPC variable in whatever way we need

I am eager to combine this library with my library https://github.com/BeerMoneyDev/nest-remix

On Sun, Feb 5, 2023, 3:47 AM Darko Stojkovski @.***> wrote:

I can see how this will get the server set-up and responding, but how do we achieve what tRPC primarily achieves, which is sharing the types between the client and the server?

— Reply to this email directly, view it on GitHub https://github.com/macstr1k3r/trpc-nestjs-adapter/issues/1#issuecomment-1417259024, or unsubscribe https://github.com/notifications/unsubscribe-auth/AARU6RJT6U3RB4C2B74IVBLWV5ZLXANCNFSM6AAAAAAUN3UCEE . You are receiving this because you authored the thread.Message ID: @.***>

KerryRitter commented 1 year ago

I see, so somehow we need to get the type-safety passed into the AppRouter definition. I'll have a thought on this.

KerryRitter commented 1 year ago

Not having any good ideas coming to me on how to do this with decorators.

I am thinking it may be a good pattern to do a .trpc.ts file per modules, and then merge them in the app root. This will at least keep things module. So:

-- app.module.ts -- app.trpc.ts -- cats/ ---- cats.module.ts ---- cats.trpc.ts

robert-king commented 1 year ago

is it possible to use something equivalent to @UseGuards(AuthGuard('jwt'))

I tried messing around with trpc context, middleware, nestjs execution context etc, but i'm very new to nestjs and trpc.

Here are two relevant links: https://github.com/nestjs/passport/blob/020c82532cfcf75336118e6861555b3d016908b3/lib/auth.guard.ts https://trpc.io/docs/authorization


export function createContext({
                                      req,
                                      res,
                                    }: trpcNext.CreateNextContextOptions) {
  // Create your context based on the request object
  // Will be available as `ctx` in all your resolvers
  // This is just an example of something you might want to do in your ctx fn
  // async function getUserFromHeader() {
  //   if (req.headers.authorization) {
  //     const user = await decodeAndVerifyJwtToken(
  //       req.headers.authorization.split(' ')[1],
  //     );
  //     return user;
  //   }
  //   return null;
  // }
  // const user = await getUserFromHeader();
  // return {
  //   user,
  // };
  // return Promise.resolve({name: 'zing'});
  return 'wat';
}
type Context = InferContextType<typeof createContext>;

// type CtxType = InferContextType<typeof createContext>;
// const createContext = () => ({});
const trpc = initTRPC.context<Context>().create();

const isAuthed = trpc.middleware(async ({ next, ctx }) => {
  let executionContext = await ctx.resolveNestDependency(???)
  const memoizedGuard = new (AuthGuard('jwt'));
  await memoizedGuard.canActivate(executionContext)
  // if (!ctx.user?.isAdmin) {
  //   throw new TRPCError({ code: 'UNAUTHORIZED' });
  // }
  return next({
    ctx: {
      user: ??
    },
  });
});
const { router } = trpc;
const privateProcedure = trpc.procedure.use(isAuthed);
KevinEdry commented 1 year ago

@KerryRitter Decorators are not really possible with tRpc, since decorators are applied in runtime, and tRpc benefits tremendously from its compile-time features. You can't infer a typescript type based on decorators, so the only feasible way to achieve this if someone will generate the AppRouter type in build-time and save it locally similar to Prisma's solution (which isn't ideal either since this will require you to reset Typescript each time you change something).

The other option is to type the router AND the controller, in fact this is the implementation tsrest landed on, see: https://ts-rest.com/docs/nest Which is even worse then the solutions I've mentioned above since if I am already writing the router, why should I write the controller as well?

KerryRitter commented 1 year ago

Yep I had a completely off base understanding of tRPC - but a boy can dream haha. I will check out this other project. Thank you!