dataloader

package
v1.22.1 Latest Latest
Warning

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

Go to latest
Published: Jun 22, 2026 License: MIT Imports: 2 Imported by: 0

Documentation

Overview

Package dataloader coalesces N individual key lookups into one batched fetch, eliminating the N+1 query pattern that GraphQL's nested resolvers otherwise produce.

Two pieces:

  1. Loader[K, V] — holds a fetch function and per-request state. Load(key) enqueues the key and returns a thunk; the first thunk to run dispatches one batched fetch for every enqueued key.

  2. Registry — per-request map of loaders, attached to context via WithRegistry. Get[K, V] looks up (or lazily creates) a named loader on the registry so multiple resolvers in one request share one Loader instance.

Wired into the GraphQL transport via a request middleware that drops a fresh Registry on every POST /graphql. graphql-go's thunk-aware executor calls deferred resolvers breadth-first after the synchronous pass, which is exactly the batching window the Loader exploits — no goroutines, no timeouts, no surprises.

Example, inside a GraphQL resolver:

loader := dataloader.Get[int64, *BankDetail](p.Context, "bankByUserID",
    func(ctx context.Context, userIDs []int64) (map[int64]*BankDetail, error) {
        return db.BankDetailsByUserIDs(ctx, userIDs)
    })
thunk := loader.Load(user.ID)
return thunk, nil

50 users in a list query → 1 BankDetailsByUserIDs call.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func WithRegistry

func WithRegistry(ctx context.Context, reg *Registry) context.Context

WithRegistry attaches reg to ctx. The GraphQL middleware calls this once per request; resolvers retrieve the registry implicitly via Get. Returns a new context derived from ctx.

Types

type Fetch

type Fetch[K comparable, V any] func(ctx context.Context, keys []K) (map[K]V, error)

Fetch is the batched lookup the Loader memoizes. It receives every distinct key seen during one Loader's lifetime and must return a map keyed by those same keys. Missing keys in the returned map are surfaced as the zero V (the resolver decides whether that means "null" or "not found").

Errors propagate to every thunk attached to this loader instance — one fetch failure fails every caller in that request, which matches the dataloader-spec semantics and prevents partial-render confusion.

type Loader

type Loader[K comparable, V any] struct {
	// contains filtered or unexported fields
}

Loader is the per-(request, name) batcher. Construct via Get; do not create directly — the registry handles lifecycle so siblings share a single instance.

func Get

func Get[K comparable, V any](ctx context.Context, name string, fetch Fetch[K, V]) *Loader[K, V]

Get returns the registered loader for (ctx, name), creating it on the first call within a request and reusing it on subsequent calls. The fetch function is captured on first call; second-and- later calls with different fetch functions silently get the first-registered one, which keeps siblings batching together.

When the registry isn't on ctx (no middleware), Get returns a brand-new Loader every call — batching still works within one resolver's scope, but cross-resolver sharing is lost. Convenient for tests, wrong for production: install the middleware.

func New

func New[K comparable, V any](fetch Fetch[K, V]) *Loader[K, V]

New constructs a standalone Loader. Most callers should use dataloader.Get(ctx, name, fetch) instead — it shares one Loader across every resolver in the same request, which is the whole point of the pattern. New is here for tests + advanced callers that manage lifecycle by hand.

func (*Loader[K, V]) Load

func (l *Loader[K, V]) Load(key K) func() (interface{}, error)

Load enqueues key for the next batch and returns a thunk that, when called, returns the value for that key. The thunk is intended to be returned directly from a graphql-go resolver — the executor dethunks breadth-first after all sibling resolvers have run, which is exactly when every key for this batch is in the queue.

Duplicate keys are deduped: 50 thunks for the same user.ID enqueue the key once and share the result. The caller doesn't have to pre-uniqueify upstream.

func (*Loader[K, V]) LoadCtx

func (l *Loader[K, V]) LoadCtx(ctx context.Context, key K) func() (interface{}, error)

LoadCtx is Load with an explicit context for the batch call. Most resolvers pass the GraphQL request context; this overload exists so the batch call can use a different one (e.g. a longer-lived background ctx during cleanup). First call wins — siblings calling LoadCtx with different contexts get the first one for the batch.

func (*Loader[K, V]) Prime

func (l *Loader[K, V]) Prime(key K, value V)

Prime seeds the loader's cache with a key/value pair. Useful when a previous query already fetched the data and you want subsequent Load calls in the same request to skip the fetch.

type Registry

type Registry struct {
	// contains filtered or unexported fields
}

Registry is the per-request map of loaders. The GraphQL transport's request middleware drops a fresh Registry on the context for every POST /graphql; resolvers reach into it via Get to share a single Loader instance with their siblings.

Methods are safe for concurrent use. A nil receiver makes Get return a one-shot Loader (no sharing) — useful in tests that don't install the middleware, but pointless in production.

func FromContext

func FromContext(ctx context.Context) *Registry

FromContext returns the registry stashed by WithRegistry, or nil if the middleware didn't run for this request. Most callers should use Get instead; FromContext is exposed for advanced use (e.g. manually priming several loaders from a top-level resolver).

func NewRegistry

func NewRegistry() *Registry

NewRegistry returns an empty per-request registry.

func (*Registry) Names

func (r *Registry) Names() []string

Names returns the names of every loader registered in this request. Useful for tests and the dashboard's per-request introspection.

Jump to

Keyboard shortcuts

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