go 113 lines · 3 tabs

Cursor-Based Directory Listing API in Go with net/http

Shared by codesnips Sep 2026
3 tabs
package listing

import "encoding/base64"

func Encode(name string) string {
	return base64.RawURLEncoding.EncodeToString([]byte(name))
}

func Decode(cursor string) (string, error) {
	b, err := base64.RawURLEncoding.DecodeString(cursor)
	if err != nil {
		return "", err
	}
	return string(b), nil
}
3 files · go Explain with highlit

This snippet shows how a directory listing endpoint is built in Go using cursor-based pagination rather than offset pagination. Offset pagination re-scans and skips rows, which is both slow and unstable when the underlying set changes between requests; a cursor encodes the exact position to resume from, so each page starts precisely where the last one ended. For a filesystem listing, the natural stable ordering is by name, so the cursor is simply the last filename returned, base64-encoded to keep it opaque and URL-safe.

The cursor.go file isolates the encoding concern. Encode wraps a filename in base64.RawURLEncoding, and Decode reverses it, returning an error on malformed input. Treating the cursor as opaque means clients never construct it themselves; they only echo back the next_cursor the server handed them, which lets the server change its internal format later without breaking callers.

The lister.go file does the actual work in List. It reads the directory with os.ReadDir, which returns entries already sorted by name — a convenient guarantee that makes the name cursor valid. When a cursor is present it decodes it and uses sort.Search to binary-search for the first entry strictly greater than the cursor value, skipping everything already seen. It then collects up to limit entries into FileInfo records, calling entry.Info() to pull size and mod time. If more entries remain past the page, it sets NextCursor from the last item's name; an empty NextCursor signals the end.

The handler.go file wires this into net/http. ListHandler parses limit and cursor from the query string, clamps limit to a sane maximum to prevent a client from requesting an unbounded page, and translates a bad cursor into a 400 rather than a 500. Success responses are marshaled with json.NewEncoder. The design is fully stateless — no server-side session holds pagination state — so it scales horizontally and survives restarts, at the cost of not supporting arbitrary jumps to page N.


Related snips

Share this code

Here's the card — post it anywhere.

Cursor-Based Directory Listing API in Go with net/http — share card
Link copied