Graphlink
Type-safe code generator for GraphQL schemas — produces clients and server interfaces for Dart, Flutter, Java, and Spring Boot. Features built-in caching with TTL/tag-based invalidation, JSON serialization, and auto-generated fragments.
Install / Use
/learn @Oualitsen/GraphlinkREADME

GraphLink
Define your GraphQL schema once. Get a fully typed client and server scaffold — for Dart, Flutter, Java, and Spring Boot — in seconds.
No runtime. No boilerplate. No schema drift.
GraphLink is a CLI tool (glink) that reads a .graphql file and writes production-ready, idiomatic code for your target language. The generated files have zero dependency on GraphLink itself — delete it tomorrow, everything still compiles.
Why GraphLink?
No generics at the Java call site.
Every other Java GraphQL client makes you write TypeReference<GraphQLResponse<Map<String,Object>>>. GraphLink generates fully-resolved return types:
// Other clients
GraphQLResponse<Map<String, Object>> res = client.query(QUERY_STRING, vars, new TypeReference<>() {});
Vehicle v = objectMapper.convertValue(res.getData().get("getVehicle"), Vehicle.class);
// GraphLink
GetVehicleResponse res = client.queries.getVehicle("42");
System.out.println(res.getGetVehicle().getBrand());
Cache control belongs in your schema.
Declare caching once with @glCache and @glCacheInvalidate — the generated client handles TTL, tag-based invalidation, partial query caching, and offline fallback automatically.
Only what the server needs. GraphLink generates minimal, precise query strings. No full-schema dumps that break Spring Boot's strict GraphQL validation.
Single source of truth.
One .graphql file drives the Dart client, the Java client, and the Spring Boot controllers + service interfaces. Add a field once, regenerate, and both ends stay in sync.
Supported targets
| Target | Status | |---|---| | Dart client | Stable | | Flutter client | Stable | | Java client | Stable | | Spring Boot server | Stable | | TypeScript client | In development | | Express / Node.js | Planned | | Go, Kotlin | Planned |
Installation
Download the single self-contained binary — no JVM, no package manager required.
# macOS (ARM)
curl -fsSL https://github.com/Oualitsen/graphlink/releases/latest/download/glink-macos-arm64 -o glink
chmod +x glink && sudo mv glink /usr/local/bin/glink
# Linux (x64)
curl -fsSL https://github.com/Oualitsen/graphlink/releases/latest/download/glink-linux-x64 -o glink
chmod +x glink && sudo mv glink /usr/local/bin/glink
Also available: glink-macos-x64, glink-linux-arm64, glink-windows-x64.exe
→ All releases
Flutter / Dart projects can also use the Dart package:
flutter pub add --dev graphlink
# or
pub add --dev graphlink
Quick Start
1. Write your schema
type Vehicle {
id: ID!
brand: String!
model: String!
year: Int!
fuelType: FuelType!
}
enum FuelType { GASOLINE DIESEL ELECTRIC HYBRID }
input AddVehicleInput {
brand: String!
model: String!
year: Int!
fuelType: FuelType!
}
type Query {
getVehicle(id: ID!): Vehicle! @glCache(ttl: 120, tags: ["vehicles"])
listVehicles: [Vehicle!]! @glCache(ttl: 60, tags: ["vehicles"])
}
type Mutation {
addVehicle(input: AddVehicleInput!): Vehicle! @glCacheInvalidate(tags: ["vehicles"])
}
2. Configure
{
"schemaPaths": ["schema/*.graphql"],
"mode": "client",
"typeMappings": { "ID": "String", "Float": "double", "Int": "int", "Boolean": "bool" },
"outputDir": "lib/generated",
"clientConfig": {
"dart": {
"packageName": "my_app",
"generateAllFieldsFragments": true,
"autoGenerateQueries": true
}
}
}
3. Generate
glink -c config.json # once
glink -c config.json -w # watch mode — regenerate on every save
That's it. You get typed classes, a ready-to-use client, JSON serialization, and cache wiring — all generated.
Usage
Dart / Flutter
// Initialize — one line with generated adapters
final client = GraphLinkClient.withHttp(
url: 'http://localhost:8080/graphql',
wsUrl: 'ws://localhost:8080/graphql',
tokenProvider: () async => await getAuthToken(), // optional
);
// Query — fully typed, no casting
final res = await client.queries.getVehicle(id: '42');
print(res.getVehicle.brand); // Toyota
print(res.getVehicle.fuelType); // FuelType.GASOLINE
// Mutation
final added = await client.mutations.addVehicle(
input: AddVehicleInput(brand: 'Toyota', model: 'Camry', year: 2023, fuelType: FuelType.GASOLINE),
);
// Subscription
client.subscriptions.vehicleAdded().listen((e) => print(e.vehicleAdded.brand));
Java
// One-liner init — Jackson + Java 11 HttpClient auto-configured
GraphLinkClient client = new GraphLinkClient("http://localhost:8080/graphql");
// Query — no generics, no casting
GetVehicleResponse res = client.queries.getVehicle("42");
System.out.println(res.getGetVehicle().getBrand());
// Mutation — builder pattern
client.mutations.addVehicle(
AddVehicleInput.builder()
.brand("Toyota").model("Camry").year(2023).fuelType(FuelType.GASOLINE)
.build()
);
// List
List<Vehicle> vehicles = client.queries.listVehicles().getListVehicles();
Spring Boot (server mode)
Set "mode": "server" and GraphLink generates controllers, service interfaces, types, inputs, and enums:
// Generated — implement this interface
public interface VehicleService {
Vehicle getVehicle(String id);
List<Vehicle> listVehicles();
Vehicle addVehicle(AddVehicleInput input);
Flux<Vehicle> vehicleAdded(); // subscriptions use Reactor Flux
}
// Generated — wires directly into Spring GraphQL
@Controller
public class VehicleServiceController {
@QueryMapping
public Vehicle getVehicle(@Argument String id) { return vehicleService.getVehicle(id); }
// ...
}
Just implement the service interface — the routing is done.
Built-in Caching
Cache control lives in the schema, not scattered through your application code.
type Query {
# Cache for 2 minutes, tagged "vehicles"
getVehicle(id: ID!): Vehicle! @glCache(ttl: 120, tags: ["vehicles"])
# Serve stale data when offline instead of throwing
getUserProfile(id: ID!): UserProfile @glCache(ttl: 60, staleIfOffline: true)
}
type Mutation {
# Evicts all "vehicles" cache entries on success
addVehicle(input: AddVehicleInput!): Vehicle! @glCacheInvalidate(tags: ["vehicles"])
# Wipe everything
resetData: Boolean! @glCacheInvalidate(all: true)
}
Cache entries are keyed by operation name + variables — each unique argument combination is cached independently. The generated client handles all of it automatically. Bring your own persistent store by implementing GraphLinkCacheStore.
How It Compares
| Feature | GraphLink | ferry (Dart) | Apollo (JS/Kotlin) | Manual | |---|---|---|---|---| | Runtime dependency | None | Yes | Yes | None | | Sends whole schema per request | No | Yes | Partial | No | | Generics at Java call site | No | N/A | Yes | Yes | | Server-side generation | Yes | No | Partial | Manual | | Java client | Yes | No | Kotlin only | Manual | | Cache directives in schema | Yes | No | No | No | | Spring Boot controller gen | Yes | No | No | Manual |
Documentation
Full documentation at graphlink.dev
- Getting Started — install, first schema, run generator
- Dart / Flutter Client — adapters, queries, mutations, subscriptions
- Java Client — no-generics API, builder pattern, adapters
- Spring Boot Server — controllers, service interfaces, subscriptions
- Caching —
@glCache,@glCacheInvalidate, partial caching, offline - Directives Reference — all 13 directives with examples
- Configuration Reference — every config key explained
License
MIT — see LICENSE.
Issues and contributions welcome at github.com/Oualitsen/graphlink.
Related Skills
node-connect
354.3kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
112.3kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
354.3kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
354.3kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
