cd..blog

Real-Time Mastery: AdonisJS, Supabase & GCP with TypeScript

const published = "Dec 30, 2025, 10:21 PM";const readTime = 5 min;
adonisjssupabasegcptypescriptwebsockets
Build scalable real-time applications using AdonisJS, Supabase, and GCP, all powered by TypeScript for robust, type-safe development.

Real-Time Mastery: AdonisJS, Supabase & GCP with TypeScript

Building modern web applications often demands real-time capabilities, robust data management, and scalable deployment. This post demonstrates how to integrate AdonisJS, Supabase, Google Cloud Platform (GCP), and WebSockets, all within a TypeScript ecosystem, to create a powerful real-time application.

Architecture Overview

Our architecture leverages a synergistic blend of technologies:

  • AdonisJS: A full-stack Node.js framework, providing a robust backend API and a built-in WebSocket server for real-time communication. AdonisJS Docs
  • TypeScript: The primary language for our entire stack, ensuring type safety, better tooling, and improved code maintainability. TypeScript Docs
  • Supabase: An open-source Firebase alternative offering a PostgreSQL database, real-time capabilities (via PostgreSQL's logical replication), authentication, and storage. Supabase Docs
  • Google Cloud Platform (GCP): Our chosen cloud provider for deploying and scaling the AdonisJS backend, leveraging services like Cloud Run. GCP Docs
  • WebSockets: The protocol enabling persistent, bi-directional communication between clients and our AdonisJS server for instantaneous updates.

In this setup, AdonisJS acts as our application server and WebSocket gateway. It interacts with Supabase for data persistence and subscribes to Supabase's real-time changes. Upon receiving a database update, AdonisJS broadcasts this information to connected clients via its own WebSockets. GCP hosts our containerized AdonisJS application.

AdonisJS Backend with WebSockets

AdonisJS provides a first-class WebSocket implementation, making it straightforward to set up real-time communication channels. First, ensure you have AdonisJS installed with TypeScript support.

# Create a new AdonisJS project
npm init adonis-ts-app@latest real-time-app
cd real-time-app

# Install WebSocket provider
npm i @adonisjs/websocket
node ace configure @adonisjs/websocket

Define a WebSocket channel (e.g., app/Controllers/Ws/ChatController.ts) to handle client connections and messages:

// app/Controllers/Ws/ChatController.ts
import { WsContext } from '@adonisjs/websocket/types'
import Ws from '@ioc:Adonis/Core/Ws'

interface ChatMessage {
  id: string;
  userId: string;
  content: string;
  createdAt: string;
}

export default class ChatController {
  public async onConnect(ctx: WsContext) {
    console.log(`Client connected: ${ctx.socket.id}`)
    ctx.socket.send('Welcome to the chat channel!')
  }

  public async onMessage(ctx: WsContext, payload: { content: string }) {
    console.log(`Received from ${ctx.socket.id}: ${payload.content}`)
    // In a real app, save to DB first, then broadcast
    // For now, we'll let Supabase trigger the broadcast via its real-time events
  }

  public async onClose(ctx: WsContext) {
    console.log(`Client disconnected: ${ctx.socket.id}`)
  }
}

Configure your start/ws.ts to use this controller:

// start/ws.ts
import Ws from '@ioc:Adonis/Core/Ws'

Ws.start((router) => {
  router.channel('chat', 'ChatController')
})

Supabase Real-Time Integration

Supabase's Realtime engine allows your AdonisJS backend to subscribe to database changes. This is crucial for broadcasting updates originating from various sources (e.g., another service, a direct database insert).

First, create a Supabase project and enable Realtime for your messages table. Then, install supabase-js:

npm i @supabase/supabase-js

Create a service in AdonisJS to listen for Supabase events:

// app/Services/SupabaseListener.ts
import { createClient } from '@supabase/supabase-js'
import Env from '@ioc:Adonis/Core/Env'
import Ws from '@ioc:Adonis/Core/Ws'

interface SupabaseMessage {
  id: string;
  user_id: string; // Supabase column names often use snake_case
  content: string;
  created_at: string;
}

class SupabaseListener {
  private supabase = createClient(
    Env.get('SUPABASE_URL'),
    Env.get('SUPABASE_ANON_KEY')
  )

  public start() {
    this.supabase
      .channel('public:messages') // Listen to the 'messages' table in the 'public' schema
      .on<SupabaseMessage>('postgres_changes', { event: 'INSERT', schema: 'public', table: 'messages' }, (payload) => {
        console.log('New message from Supabase:', payload.new)
        // Broadcast the new message to all connected WebSocket clients via AdonisJS
        Ws.io.emit('message', payload.new) // 'message' is the event clients will listen for
      })
      .subscribe()

    console.log('Supabase Realtime listener started.')
  }
}

export default new SupabaseListener()

Initialize this listener in your start/app.ts or a custom provider:

// providers/AppProvider.ts (example)
import { ApplicationContract } from '@ioc:Adonis/Core/Application'

export default class AppProvider {
  constructor(protected app: ApplicationContract) {}

  public async boot() {
    if (this.app.environment === 'web') {
      const SupabaseListener = await import('App/Services/SupabaseListener')
      SupabaseListener.default.start()
    }
  }
}

GCP Deployment with Cloud Run

Deploying AdonisJS with WebSockets on GCP can be efficiently done using Cloud Run. Cloud Run supports long-lived connections required for WebSockets. Containerize your AdonisJS application using a Dockerfile:

# Dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY . .
RUN node ace build --production
ENV NODE_ENV=production
EXPOSE 3333
CMD ["node", "build/server.js"]

Build and push your Docker image to Google Container Registry or Artifact Registry, then deploy to Cloud Run, ensuring you allocate enough CPU and memory for WebSocket connections. For highly scalable real-time applications across multiple Cloud Run instances, consider using Google Cloud Pub/Sub to coordinate broadcasts between instances.

Best Practices & Conclusion

  • Authentication: Integrate Supabase Auth with AdonisJS sessions for secure WebSocket connections.
  • Error Handling: Implement robust error handling for both WebSocket and Supabase interactions.
  • Scalability: While Cloud Run handles scaling, for extreme loads, consider a dedicated message broker (e.g., Redis Pub/Sub, GCP Pub/Sub) to fan out messages across multiple AdonisJS instances.

By combining AdonisJS's powerful backend and WebSocket capabilities with Supabase's real-time database and GCP's scalable infrastructure, all within the type-safe realm of TypeScript, you can build incredibly robust and responsive real-time applications that stand ready for 2025's demands and beyond.