Documentation
Node.js
Express

Expose any Node.js HTTP server — Express, Fastify, Koa, Hono, NestJS, Hapi, and more — using mekong-cli. If it listens on a port, it works with MekongTunnel.

Quick Start

npm install -g mekong-cli
mekong auth YOUR_TOKEN
# Start your server (e.g. Express on 3000)
node server.js
 
# In another terminal
mekong 3000
# → https://happy-tiger-a1b2c3d4.mekongtunnel.dev

Express

npm install express
// server.ts
import express from 'express'
 
const app = express()
app.use(express.json())
 
app.get('/', (req, res) => {
  res.json({ message: 'Hello from Express via MekongTunnel!' })
})
 
app.post('/webhook', (req, res) => {
  console.log('Received:', req.body)
  res.json({ status: 'ok' })
})
 
app.listen(3000, () => {
  console.log('Server running on http://localhost:3000')
})
npx ts-node server.ts
mekong 3000

Express + Auto-Tunnel

// server.ts
import express from 'express'
import { createTunnel } from 'mekong-cli'
 
const app = express()
app.use(express.json())
 
app.get('/', (req, res) => res.json({ ok: true }))
 
app.listen(3000, async () => {
  const tunnel = await createTunnel({ port: 3000 })
  console.log('Local:  http://localhost:3000')
  console.log('Tunnel:', tunnel.url)
})

package.json Script

{
  "scripts": {
    "dev": "nodemon server.ts",
    "tunnel": "mekong 3000",
    "dev:share": "concurrently \"nodemon server.ts\" \"mekong 3000\""
  }
}

Fastify

npm install fastify
// server.ts
import Fastify from 'fastify'
 
const fastify = Fastify({ logger: true })
 
fastify.get('/', async () => {
  return { message: 'Hello from Fastify via MekongTunnel!' }
})
 
fastify.post('/webhook', async (request) => {
  console.log('Webhook:', request.body)
  return { status: 'ok' }
})
 
fastify.listen({ port: 3000, host: '0.0.0.0' }, async (err) => {
  if (err) process.exit(1)
  const { createTunnel } = await import('mekong-cli')
  const tunnel = await createTunnel({ port: 3000 })
  console.log('Tunnel:', tunnel.url)
})

Fastify Plugins

import cors from '@fastify/cors'
import helmet from '@fastify/helmet'
 
await fastify.register(cors, { origin: '*' })
await fastify.register(helmet)

Koa

npm install koa @koa/router koa-bodyparser
import Koa from 'koa'
import Router from '@koa/router'
import bodyParser from 'koa-bodyparser'
import { createTunnel } from 'mekong-cli'
 
const app = new Koa()
const router = new Router()
 
app.use(bodyParser())
 
router.get('/', (ctx) => {
  ctx.body = { message: 'Hello from Koa!' }
})
 
router.post('/webhook', (ctx) => {
  console.log('Received:', ctx.request.body)
  ctx.body = { ok: true }
})
 
app.use(router.routes())
 
app.listen(3000, async () => {
  const tunnel = await createTunnel({ port: 3000 })
  console.log('Tunnel:', tunnel.url)
})

Hono

Hono (opens in a new tab) is a fast, ultralight web framework for Node.js, Cloudflare Workers, Deno, and Bun.

npm install hono @hono/node-server
import { Hono } from 'hono'
import { serve } from '@hono/node-server'
import { createTunnel } from 'mekong-cli'
 
const app = new Hono()
 
app.get('/', (c) => c.json({ message: 'Hello from Hono!' }))
 
app.post('/webhook', async (c) => {
  const body = await c.req.json()
  console.log('Webhook:', body)
  return c.json({ ok: true })
})
 
serve({ fetch: app.fetch, port: 3000 }, async () => {
  const tunnel = await createTunnel({ port: 3000 })
  console.log('Tunnel:', tunnel.url)
})

NestJS

npm install -g @nestjs/cli
nest new my-app
// src/main.ts
import { NestFactory } from '@nestjs/core'
import { AppModule } from './app.module'
import { createTunnel } from 'mekong-cli'
 
async function bootstrap() {
  const app = await NestFactory.create(AppModule)
  app.enableCors()
 
  await app.listen(3000)
 
  const tunnel = await createTunnel({ port: 3000 })
  console.log('Local:  http://localhost:3000')
  console.log('Tunnel:', tunnel.url)
  console.log('Swagger:', tunnel.url + '/api')
}
bootstrap()

NestJS Swagger UI works through the tunnel at /api.


Stripe Webhooks (Generic)

For any Express-compatible server:

import express from 'express'
import Stripe from 'stripe'
 
const app = express()
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)
 
// IMPORTANT: raw body required for Stripe signature verification
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.headers['stripe-signature']!
 
  const event = stripe.webhooks.constructEvent(
    req.body, sig, process.env.STRIPE_WEBHOOK_SECRET!
  )
 
  switch (event.type) {
    case 'payment_intent.succeeded':
      console.log('Payment succeeded!')
      break
    case 'customer.subscription.created':
      console.log('New subscription!')
      break
  }
 
  res.json({ received: true })
})
 
app.use(express.json()) // Apply after raw middleware
 
app.listen(3000)
mekong 3000
# Register: https://your-tunnel.mekongtunnel.dev/webhook → Stripe Dashboard

Framework Comparison

FrameworkStylePerformanceBest For
ExpressMiddlewareGoodAPIs, webhooks, general use
FastifyPluginExcellentHigh-throughput APIs
KoaMiddleware (async)GoodLightweight modern APIs
HonoHandlerExcellentMulti-runtime, edge
NestJSDecorator/ModuleGoodEnterprise, large apps

All work identically with mekong PORT.