Help us shape the future of Go! Take the 2026 Go Developer Survey

ngrokgin

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 24, 2026 License: MIT Imports: 4 Imported by: 0

README

ngrok-gin

Serve a Gin router through an ngrok endpoint with one call:

package main

import (
	"context"

	"github.com/gin-gonic/gin"
	ngrokgin "github.com/ngrok-samples/ngrok-gin"
)

func main() {
	router := gin.Default()
	router.GET("/", func(c *gin.Context) { c.String(200, "hello") })
	ngrokgin.Serve(context.Background(), router, ngrokgin.Config{})
}

What it is

A thin Gin-native ngrok helper. ngrok-go's ngrok.EndpointListener is a real net.Listener, and Gin's router.RunListener already accepts one directly — so Serve/ServeMany don't need a separate upstream port or a manual connection-serving loop the way the Axum and FastAPI siblings do. Confirmed live: router.RunListener(ln) round-trips a real HTTP request through the public ngrok URL.

Setup

  1. Sign up at ngrok.com and grab an authtoken from the dashboard.
  2. Set it as an env var:
    NGROK_AUTHTOKEN=your_token_here
    
  3. go get github.com/ngrok-samples/ngrok-gin and call Serve — see examples/basic/main.go.

You'll get a public URL bound to your ngrok account's default dev domain if you don't specify one.

Config

config := ngrokgin.Config{
	URL:     "your-reserved-domain.ngrok.app", // "" = account's default dev domain
	Pooling: false,
	TrafficPolicy: `
on_http_request:
  - actions:
      - type: basic-auth
        config:
          credentials:
            - "user:password123"
`,
	Binding: "", // ngrokgin.BindingInternal, etc.
}

ngrokgin.Serve(context.Background(), router, config)

See examples/with_config/main.go for a working example.

Field Type Description
URL string Reserved domain for this endpoint. "" falls back to the account's default dev domain.
Pooling bool Opt in to ngrok endpoint pooling — required if another endpoint on the same session would otherwise collide on the same domain. See Collisions below.
TrafficPolicy string A raw ngrok Traffic Policy document (YAML or JSON) — the mechanism for auth, IP restrictions, header manipulation, webhook verification, and more.
Binding Binding BindingPublic / BindingInternal / BindingKubernetes ingress configuration. Not part of Traffic Policy (checked ngrok's actions reference directly — no equivalent exists), so it stays a standalone field. BindingInternal requires URL to end in .internal, enforced by ngrok itself (ERR_NGROK_9029 if it doesn't).

Multiple endpoints

ServeMany opens one endpoint per [Endpoint], each with its own *gin.Engine and Config, all on a single agent, served concurrently:

ngrokgin.ServeMany(context.Background(), []ngrokgin.Endpoint{
	{Router: appA, Config: ngrokgin.Config{URL: "app.mycompany.ngrok.app"}},
	{Router: appB, Config: ngrokgin.Config{URL: "api.mycompany.ngrok.app"}},
})

Serve(ctx, router, config) is just ServeMany(ctx, []Endpoint{{router, config}}) — the one-endpoint case of the same function. See examples/multi/main.go (two distinct domains, two distinct apps) and examples/pooling/main.go (one domain, Pooling: true, two apps sharing it with load-balanced routing).

Collisions

Two distinct failure modes to know about:

  • Same-session, no domain / same domain, no pooling: the raw SDK does not error on this — confirmed live against the real SDK, isolated from this package, that two listeners opened this way both succeed with the identical URL, no error either time, only the most recently opened one actually receiving traffic. ServeMany guards against it itself: pass two endpoints that would collide without Pooling: true on both, and it returns a clear error immediately, before opening any listener. See examples/collision/main.go for the failing case and examples/pooling/main.go for the working one, both live-tested.
  • Cross-session claim (another running agent, or a dashboard-configured Cloud Endpoint, already bound to that domain): this does error, loudly — ERR_NGROK_334: "already online... stop your existing endpoint first, or start both endpoints with --pooling-enabled". If you hit this and nothing on your machine looks like it's using that domain, check the ngrok dashboard for a persistent Cloud Endpoint configured on it.

Development

go build ./...
go test ./...
go run ./examples/basic

License

MIT

Documentation

Overview

Package ngrokgin serves a Gin router through an ngrok endpoint with one call.

ngrok-go's ngrok.EndpointListener is a real net.Listener, and Gin's gin.Engine.RunListener already accepts one directly — so unlike the Axum and FastAPI siblings, there's no separate upstream port or manual connection-serving loop to write. Confirmed live: WithPoolingEnabled, WithBindings, WithTrafficPolicy, and WithURL all exist on ngrok-go v2 with parity to the other SDKs, and router.RunListener(ln) round-trips a real request through the public ngrok URL.

package main

import (
	"context"

	"github.com/gin-gonic/gin"
	ngrokgin "github.com/ngrok-samples/ngrok-gin"
)

func main() {
	router := gin.Default()
	router.GET("/", func(c *gin.Context) { c.String(200, "hello") })
	ngrokgin.Serve(context.Background(), router, ngrokgin.Config{})
}

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Serve

func Serve(ctx context.Context, router *gin.Engine, config Config) error

Serve opens a single ngrok endpoint per config and serves router on it. Shorthand for the one-endpoint case of ServeMany. Blocks until the listener closes or a connection-level error occurs.

func ServeMany

func ServeMany(ctx context.Context, endpoints []Endpoint) error

ServeMany opens one ngrok endpoint per entry in endpoints, all on a single agent, and serves each endpoint's router concurrently. Blocks until every listener closes or a connection-level error occurs, returning the first such error.

Types

type Binding

type Binding string

Binding is the endpoint's ingress binding. Not part of ngrok's Traffic Policy — confirmed by checking the Traffic Policy actions reference directly, so this stays a standalone field rather than folded into TrafficPolicy.

const (
	BindingPublic Binding = "public"
	// BindingInternal requires URL to end in .internal — enforced by ngrok
	// itself (ERR_NGROK_9029 if it doesn't), not something this package
	// validates.
	BindingInternal   Binding = "internal"
	BindingKubernetes Binding = "kubernetes"
)

type CollisionError

type CollisionError struct {
	Indices []int
	URL     string
}

CollisionError is returned by Serve or ServeMany, before any ngrok session is opened, when two or more configs would silently collide on the same URL without pooling enabled.

ngrok does not reject or warn when two listeners in the same session end up on the same public url — it silently lets the most recently opened listener win, and the other becomes unreachable with zero indication of why. Confirmed live against the real SDK, isolated from this package's own code (a standalone program opening two listeners with no domain, no pooling, saw both succeed with the identical URL, no error either time). A config with no explicit URL falls back to the account's default dev domain, so two URL-less configs collide there just as surely as two hardcoding the same string.

Note this is a different failure mode than a cross-session claim on a domain (another running agent, or a dashboard-configured Cloud Endpoint) — ngrok rejects that loudly on its own (ERR_NGROK_334), confirmed live; this guard only covers the silent same-session case ngrok doesn't catch.

func (*CollisionError) Error

func (e *CollisionError) Error() string

type Config

type Config struct {
	// URL is the reserved domain for this endpoint. Empty falls back to the
	// account's default dev domain.
	URL string
	// Pooling opts in to ngrok endpoint pooling — required if another
	// endpoint on the same session would otherwise land on the same domain.
	// ngrok does not reject or warn on that collision by default: confirmed
	// live against the real SDK that two listeners opened with no domain and
	// no pooling both succeed silently, returning the identical URL, with
	// only the most recently opened one actually receiving traffic.
	Pooling bool
	// TrafficPolicy is a raw ngrok Traffic Policy document (YAML or JSON),
	// passed straight through. See https://ngrok.com/docs/traffic-policy/.
	TrafficPolicy string
	Binding       Binding
}

Config configures a single ngrok endpoint.

Deliberately minimal, same reasoning as the ngrok-axum and ngrok-fastapi siblings: the many granular per-module options ngrok's SDKs used to offer (basic auth, oauth, header manipulation, CIDR restrictions, etc.) mirror ngrok's old "Edge Modules" system, superseded by Traffic Policy — use TrafficPolicy for all of that instead.

type Endpoint

type Endpoint struct {
	Router *gin.Engine
	Config Config
}

Endpoint is one router bound to one endpoint, for ServeMany. The same *gin.Engine can be reused across multiple Endpoints to pool one app behind several listeners.

Directories

Path Synopsis
examples
basic command
collision command
multi command
pooling command
with_config command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL