Getting Started
From zero to generated code in under 5 minutes.
Step 1 — Download the CLI
The GraphLink CLI is distributed as a single self-contained binary called glink. No runtime, no package manager, no JVM required.
# Download the latest release
curl -fsSL https://github.com/Oualitsen/graphlink/releases/latest/download/glink-linux-x64 -o glink
# Make it executable
chmod +x glink
# Move to your PATH
sudo mv glink /usr/local/bin/glink
# Verify
glink --version
# Download via PowerShell
Invoke-WebRequest `
-Uri "https://github.com/Oualitsen/graphlink/releases/latest/download/glink-windows-x64.exe" `
-OutFile "glink.exe"
# Add to PATH or run from current directory
.\glink.exe --version
Visit: https://github.com/Oualitsen/graphlink/releases/latest
Download the binary for your platform:
glink-linux-x64 — Linux (x86_64)
glink-linux-arm64 — Linux (ARM64 / Raspberry Pi)
glink-macos-x64 — macOS (Intel)
glink-macos-arm64 — macOS (Apple Silicon)
glink-windows-x64.exe — Windows (x86_64)
Place the binary somewhere on your PATH and make it executable.
Step 2 — Write your schema
Create a schema/ directory and save your GraphQL schema as one or more .graphql or .gql files. Here is the schema used throughout this documentation:
# An enum — GraphLink generates serialization in both directions
enum FuelType {
GASOLINE
DIESEL
ELECTRIC
HYBRID
}
# A type — generates a model class with fromJson/toJson (Dart) or builder + getters (Java)
type Person {
id: ID! # ID maps to String in both Dart and Java
name: String!
email: String!
vehicles: [Vehicle!]! # nested list — fully typed
}
type Vehicle {
id: ID!
brand: String!
model: String!
year: Int!
fuelType: FuelType!
ownerId: ID # nullable — String? in Dart, @Nullable String in Java
}
# Input types — generate immutable input classes with builders
input AddPersonInput {
name: String!
email: String!
}
input AddVehicleInput {
brand: String!
model: String!
year: Int!
fuelType: FuelType!
ownerId: ID
}
# Queries — @glCache caches the result; ttl in seconds; tags for group invalidation
type Query {
getPerson(id: ID!): Person # returns nullable Person
getVehicle(id: ID!): Vehicle! @glCache(ttl: 120, tags: ["vehicles"])
listVehicles: [Vehicle!]! @glCache(ttl: 60, tags: ["vehicles"])
}
# Mutations — @glCacheInvalidate evicts all entries with matching tags on success
type Mutation {
addPerson(input: AddPersonInput!): Person!
addVehicle(input: AddVehicleInput!): Vehicle! @glCacheInvalidate(tags: ["vehicles"])
}
# Subscriptions — backed by a WebSocket connection
type Subscription {
vehicleAdded: Vehicle!
}
The schema above exercises every major GraphLink feature: types, enums, input types, nullable vs non-nullable fields, nested lists, and caching directives.
Step 3 — Configure the generator
Create a config.json file in your project root. The config tells GraphLink where to find the schema, where to write output, and what language/framework to target.
{
"schemaPaths": ["schema/*.graphql"],
"mode": "client",
"typeMappings": {
"ID": "String",
"String": "String",
"Float": "double",
"Int": "int",
"Boolean": "bool",
"Null": "null"
},
"outputDir": "lib/generated",
"clientConfig": {
"dart": {
"packageName": "my_app",
"generateAllFieldsFragments": true,
"autoGenerateQueries": true,
"nullableFieldsRequired": false,
"immutableInputFields": true,
"immutableTypeFields": true
}
}
}
{
"schemaPaths": ["schema/*.graphql"],
"mode": "client",
"typeMappings": {
"ID": "String",
"String": "String",
"Float": "Double",
"Int": "Integer",
"Boolean": "Boolean",
"Null": "null"
},
"outputDir": "src/main/java/com/example/generated",
"clientConfig": {
"java": {
"packageName": "com.example.generated",
"generateAllFieldsFragments": true,
"autoGenerateQueries": true,
"nullableFieldsRequired": false,
"immutableInputFields": true,
"immutableTypeFields": true
}
}
}
{
"schemaPaths": ["schema/*.graphql"],
"mode": "server",
"typeMappings": {
"ID": "String",
"String": "String",
"Float": "Double",
"Int": "Integer",
"Boolean": "Boolean",
"Null": "null"
},
"outputDir": "src/main/java/com/example/generated",
"serverConfig": {
"spring": {
"basePackage": "com.example.generated",
"generateControllers": true,
"generateInputs": true,
"generateTypes": true,
"generateRepositories": false,
"immutableInputFields": true,
"immutableTypeFields": false
}
}
}
Key configuration options explained
| Key | Description |
|---|---|
schemaPaths | Glob patterns for schema files. You can split the schema across multiple files. |
mode | "client" generates client code; "server" generates Spring Boot scaffolding. |
typeMappings | Maps GraphQL scalar types to language types. Add entries for custom scalars. |
outputDir | Where to write generated files. Existing files are overwritten. |
generateAllFieldsFragments | Generates a _all_fields_TypeName fragment per type, used by autoGenerateQueries. |
autoGenerateQueries | Generates query strings for every Query/Mutation/Subscription field automatically. |
immutableInputFields | Input class fields are final (Dart) or final (Java). Inputs become builder-only. |
immutableTypeFields | Response type fields are final. For Spring server, set to false so Spring can set fields. |
generateControllers | Spring only. Generates @Controller classes wired with @QueryMapping etc. |
generateRepositories | Spring only. Generates JpaRepository interfaces for types annotated with @glRepository. |
Step 4 — Run the generator
Point glink at your config file and let it run:
glink -c config.json
For the Dart client config, the generator produces 21 files. For the Java client, 38 files. For Spring Boot, 9 files. Here is the Dart output tree:
And the Spring Boot output:
Highlighted files (✨) are the ones you interact with. Controllers are generated and never touched. Service interfaces are implemented by you. Types, inputs, and enums are plain data classes.
Step 5 — What just happened?
The generator processed each section of your schema and produced a corresponding set of files:
Types → model classes
type Vehicle became vehicle.dart (Dart) and Vehicle.java (Java). Each has all fields, a constructor, and JSON serialization. In Dart, fields are final and the constructor uses named parameters with required. In Java, an inner Builder class is generated.
Enums → enum classes with serialization
enum FuelType became fuel_type.dart and FuelType.java, each with toJson() and fromJson() methods that map to and from the GraphQL string representation.
Inputs → immutable input classes
input AddVehicleInput became add_vehicle_input.dart and AddVehicleInput.java. Required fields are enforced at construction time. The Java version uses a builder with Objects.requireNonNull for required fields.
Queries/Mutations/Subscriptions → response types + client
Each operation generates a response wrapper (e.g. GetVehicleResponse) that holds the typed result. The GraphLinkClient class exposes client.queries, client.mutations, and client.subscriptions with one method per operation.
Cache directives → wired into the client automatically
The @glCache and @glCacheInvalidate directives you wrote in the schema are reflected in the generated client code. No application-level code is needed to enable caching.
Watch mode
During development, add -w to watch your schema files and regenerate automatically on every save:
glink -c config.json -w
# Output:
# Watching schema/*.graphql for changes...
# [14:32:01] Change detected in schema/schema.graphql
# [14:32:01] Regenerating... done (21 files, 312ms)
This integrates naturally with Flutter's hot reload workflow — schema change, save, and your Dart types are updated before you switch back to the emulator.
Next steps
You now have generated code. The next step depends on your target:
- Dart / Flutter client — set up the adapter, initialize the client, make queries
- Java client — wire up Jackson or Gson, make type-safe calls
- Spring Boot server — implement the generated service interfaces, run your app
- Caching — understand TTL, tags, and partial query caching in depth