triplit icon indicating copy to clipboard operation
triplit copied to clipboard

Triplit server to export web standard Request / Response handler

Open doeixd opened this issue 10 months ago • 4 comments

Hey there,

I'm working on a SolidStart project and had an idea that might make life easier for everyone. It would be awesome if the Triplit server could run as a route handler instead of booting up its own HTTP server. This change would let it plug right into any framework that uses the web standard request/response model—think SolidStart, Astro, H3, Nuxt, and the like.

Right now, Triplit uses Hono and starts its own server via serve. What if it instead exported its app.fetch handler? Then you could simply import that handler into your project’s route file and have it handle requests like any other endpoint. This approach is neat because:

  • Flexible Integration: It would work seamlessly with any framework that uses standard request/response objects. Whether you’re building on Astro, H3, Nuxt, or SolidStart, you’d have an easy way to integrate Triplit without running a separate server.
  • Simplified Deployment: If you're deploying to serverless or edge environments, having a single request handler is much simpler than managing a dedicated server.
  • Cleaner Codebase: It keeps your project’s routing and middleware more unified, letting you manage everything in one place.

Here’s a quick sketch of what this might look like:

  1. Refactor Triplit to export a request handler:
    Create a file like src/server/triplitHandler.ts where you initialize the Hono app and export the fetch function:

    // src/server/triplitHandler.ts
    import url from 'url';
    import * as Sentry from '@sentry/node';
    import path from 'path';
    import { createRequire } from 'module';
    import { createTriplitHonoServer } from './hono.js';
    import { createNodeWebSocket } from '@hono/node-ws';
    import { Hono } from 'hono';
    
    function initSentry() {
      if (process.env.SENTRY_DSN) {
        let packageDotJson;
        try {
          const require = createRequire(import.meta.url);
          const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
          packageDotJson = require(path.join(__dirname, '../package.json'));
        } catch {}
        Sentry.init({
          dsn: process.env.SENTRY_DSN,
          release: packageDotJson?.version ?? 'unknown',
        });
      }
    }
    
    function captureException(e: any) {
      if (Sentry.isInitialized() && e instanceof Error) {
        Sentry.captureException(e);
      }
    }
    
    // Initialize Sentry if needed.
    initSentry();
    
    // Create the Hono app and set up WebSocket handling.
    const app = new Hono();
    const { upgradeWebSocket } = createNodeWebSocket({ app });
    
    // Set up the Triplit Hono server on the app.
    await createTriplitHonoServer(
      {
        // your options here
      },
      upgradeWebSocket,
      captureException,
      app
    );
    
    // Export the Hono fetch function as the request handler.
    export async function handler(request: Request): Promise<Response> {
      return app.fetch(request);
    }
    
  2. Use it in your framework’s route:
    For instance, in a SolidStart project, you’d create a route like src/routes/api/triplit.ts and delegate requests to the handler:

    // src/routes/api/triplit.ts
    import type { RequestHandler } from 'solid-start';
    import { handler as triplitHandler } from '~/server/triplitHandler';
    
    export const GET: RequestHandler = async ({ request }) => {
      return triplitHandler(request);
    };
    
    // Optionally, handle other methods like POST:
    export const POST: RequestHandler = async ({ request }) => {
      return triplitHandler(request);
    };
    

This way, the Triplit server becomes a plug-and-play request handler that can easily fit into any project that relies on the standard web request/response flow. It makes integration smoother across various frameworks and opens up cool possibilities for serverless and edge deployments.

Thanks for considering this idea! Hope it makes sense and can help simplify things for everyone.

I'm happy to try and make a PR if the project is interested in the idea, if needed.

Cheers!

doeixd avatar Apr 06 '25 22:04 doeixd

Hey thanks for the thoughts here! Now that we have the Hono integration, it makes a lot of sense to export the handlers directly. This is mostly what we're going for with our @triplit/server-core package (code) which implements all of the server logic in vanilla JS so you can build any type of server around it using whatever framework or transport you want.

With that said, there are a number of downsides to deploying your database within your web app that make it something that we don't encourage generally.

  1. For the Triplit server to work, you need durable storage / persistent file system access which most web providers like Vercel, Netlify, etc don't provide
  2. Once you integrate persistent storage into your web app, you lose the benefits of having a stateless server like easy blue/green deployments and edge rendering (which I'll admit is probably overrated for apps that hit a database anyway)
  3. WebSocket implementations are not consistent across web frameworks and web hosts. Despite there being some standardization around Request/Response setups, there is no such standardization for WebSocket setups. Hono is the closest to unifying across JS runtimes but many web hosts don't support WebSockets at all.

With all that said, if/when Triplit ends up supporting remote data stores like Cloudflare D1, Postgres, etc (which is a challenge in itself and may just not make sense in the end) and web providers start supporting websockets, this could be a really cool path in the future.

I do agree that having your database integrating as a simple library on both client and server is an exciting avenue.

matlin avatar Apr 07 '25 13:04 matlin

Thanks for the thoughtful response, @matlin, I appreciate you taking the time!

That makes sense, and I appreciate the @triplit/server-core suggestion, I'll definitely check that out.

I understand the concerns, especially regarding durable storage limitations on platforms like Vercel/Netlify and the challenges of stateless deployments. My thinking was also geared towards scenarios like containerized applications (e.g., using Docker on Fly.io, Render, or traditional VMs) where persistent volumes are readily available alongside the application server. In those cases, integrating the Triplit server as a handler within the main app could be helpful / convenient.

Regarding the WebSocket inconsistencies, have you come across crossws? It's aiming to provide a unified WebSocket adapter across different JS runtimes and frameworks, potentially addressing some of the integration friction you mentioned.

Thanks again for the detailed feedback!

Also, if (I haven't dug deeply into the implementation) Triplit can operate on top of a basic KV store, I'd look into https://unstorage.unjs.io/ they have backends/drivers for just about everything

doeixd avatar Apr 07 '25 14:04 doeixd

Makes sense. And yeah I've seen those--big fan of the UnJS project. We probably should switch to crossws. I was initially excited about unstorage but the problem is that it doesn't provide any ordered to the keys so you can't really build a database on top of it. DenoKV, on the other hand, is an attractive option and also works in Node.

matlin avatar Apr 07 '25 14:04 matlin

I will also add this would be great for projects using SvelteKit with their adapter system, especially node and bun. Although I will concede that for SvelteKit specifically, their websocket implementation is still up in the air.

As for what could be done now, it would be great to at least put Triplit server behind a specific path, as the current server solution listens to /. It would be nice to be able to pass a path argument so it can listen somewhere like /db while leaving the actual website at the root, and in the same port.

MrVauxs avatar Jul 17 '25 16:07 MrVauxs