trpc / trpc

🧙‍♀️ Move Fast and Break Nothing. End-to-end typesafe APIs made easy.
https://tRPC.io
MIT License
34.72k stars 1.24k forks source link

Fastify 3 support? #524

Closed delaneyj closed 3 years ago

delaneyj commented 3 years ago

Did you have any plans for Fastify integration? Feels like its a perfect fix as its decently low level and all TS.

I started writing a plugin but getting a little stuck in the types and the middie/express examples ported over (fastify has bridge support for express middleware ) don't seem to be working.

fastTRPC

import {
  AnyRouter,
  BaseOptions,
  CreateContextFn,
  CreateContextFnOptions,
  requestHandler,
} from "@trpc/server";
import { FastifyInstance } from "fastify";
import fp from "fastify-plugin";
import { IncomingMessage, ServerResponse } from "http";
import { URL } from "url";

export type CreateHttpContextOptions = CreateContextFnOptions<
  IncomingMessage,
  ServerResponse
>;

export type CreateHttpContextFn<TRouter extends AnyRouter> = CreateContextFn<
  TRouter,
  IncomingMessage,
  ServerResponse
>;

export interface CreateHttpHandlerOptions<TRouter extends AnyRouter>
  extends BaseOptions<TRouter, IncomingMessage> {
  prefix?: string;
  createContext: CreateHttpContextFn<TRouter>;
  router: TRouter;
}

export default fp(
  async function <TRouter extends AnyRouter>(
    app: FastifyInstance,
    opts: CreateHttpHandlerOptions<TRouter>
  ) {
    app.decorate("trpc", opts);

    if (!opts.prefix) opts.prefix = "/trpc";
    console.log({ opts });

    app.all(opts.prefix, async (req, reply) => {
      const u = new URL(req.raw.url!);
      const path = u.pathname.substr(1);
      await requestHandler({
        ...opts,
        req: req.raw,
        res: reply.raw,
        path,
      });
    });
  },
  { fastify: "3.x", name: "fastify-trpc" }
);

called using

    const app = fastify();

    app.register(fastTRPC, {
      router: appRouter,
      createContext() {
        return {};
      },
    });

    app.listen(1234);

This doesn't give any type errors but fails weirdly enough it seems to fail in the client. Never get to a breakpoint at first line in app.all() However the raw http example this is based on works fine.


testrpc/node_modules/.pnpm/@trpc+client@7.1.0-alpha.1/node_modules/@trpc/client/dist/createTRPCClient-5ae1152d.cjs.dev.js:387
        return new TRPCClientError((_message = result.error.message) !== null && _message !== void 0 ? _message : '', _objectSpread(_objectSpread({}, opts), {}, {
               ^
Error: 
    at Function.from (testrpc/node_modules/.pnpm/@trpc+client@7.1.0-alpha.1/node_modules/@trpc/client/dist/createTRPCClient-5ae1152d.cjs.dev.js:387:16)
    at testrpc/node_modules/.pnpm/@trpc+client@7.1.0-alpha.1/node_modules/@trpc/client/dist/createTRPCClient-5ae1152d.cjs.dev.js:331:58
    at processTicksAndRejections (node:internal/process/task_queues:96:5)

I don't know enough about the internals to really grok what's happening. would be nice if the npm version also exported the sourcemaps or ts code to step through.

KATT commented 3 years ago

Never actually worked with fastify so would need to dig in a bit more to understand how their requests and responses work. Seems like you return rather than .send() responses

Do you have an existing fastify-project that you want to add tRPC on or is it a greenfield project and you're used and like fastify for the non-tRPC things? Because you can do a HTTP standalone server pretty easily.

KATT commented 3 years ago

@delaneyj - see #528

delaneyj commented 3 years ago

Wow you are fast @KATT . I was afraid because it uses send + reply has a different interface than express middleware it might be a headache to do.

Reasons for Fastify

  1. At least in my testing way faster
  2. Full TS type checking
  3. Solid middleware community that is fast
  4. Plugins effect types checking and completion

The standalone http server is great but middlewares, docgen, health/status, jwt/paseto, sessions, etc are well vetted. This is for a greenfield that was starting to use giraphql, which is amazing, but there is enough dynamic route generation stuff happening that I think this could be a better fit.

So with your example the plugin is dead simple.

import {
  AnyRouter,
  CreateHttpHandlerOptions,
  requestHandler,
} from "@trpc/server";
import { FastifyInstance } from "fastify";
import fp from "fastify-plugin";

export interface CreateFastifyHandlerOptions<TRouter extends AnyRouter>
  extends CreateHttpHandlerOptions<TRouter> {
  prefix?: string;
}

export default fp(
  async function <TRouter extends AnyRouter>(
    app: FastifyInstance,
    opts: CreateFastifyHandlerOptions<TRouter>
  ) {
    app.decorate("trpc", opts);

    const { prefix, router, createContext } = opts;

    const bind = `/${prefix || "trpc"}/:path`;

    app.all(bind, async (req, reply) => {
      await requestHandler({
        req: req.raw,
        res: reply.raw,
        router,
        createContext,
        path: (req.params as any).path,
      });
    });
  },
  { fastify: "3.x", name: "fastify-trpc" }
);

use with

  const app = fastify();

  app.register(fastTRPC, {
    prefix: "foo",
    router: appRouter,
    createContext() {
      return {};
    },
  });

  app.listen(1234);

Given that plugins are a normal way to package this kind of middleware would you like me to make a PR? I could use a bit more typechecking but is on the same terms as the other server options for now.

KATT commented 3 years ago

Fastify won't make tRPC faster, but if you have api routes that you don't want to have tRPC on it's alright to do it this way.

You can make an adapter similar to the express adapter, but this is fine as well.

Great that it worked out - will close this for now.

github-actions[bot] commented 2 years ago

This issue has been locked because it had no new activity for 14 days. If you are running into a similar issue, please create a new issue. Thank you.