Skip to content

Commit 293e07a

Browse files
feat: Multi-Service GraphQL Support with Default Folder Organization & Schema Caching (#23)
* feat: add multi-service GraphQL support with folder-based organization • Add support for multiple external GraphQL services • Implement folder-based organization (default/, serviceName/) • Generate service-specific SDKs and type definitions • Add validation for external service configurations • Update Nuxt module to support TypeScript path mappings for external services • Create centralized index.ts for automatic exports • Migrate context.d.ts to context.ts with warning system • Add @graphql-tools/url-loader for remote schema loading * docs: update README with multi-service support and remove external README • Add multi-service support feature to main README features list • Remove redundant external/README.md file • Keep documentation centralized in main README * fix: resolve ESLint no-new error in URL validation - Assign new URL() result to variable instead of using for side effects - Add void operator to explicitly indicate validation-only usage * feat: add multi-service GraphQL support and update pnpm workspace dependencies * feat: add check for old GraphQL structure and provide migration instructions * fix: improve formatting in checkOldStructure function for better readability * feat: add schema download modes and optimize build performance - Add flexible download modes: 'once', 'always', 'manual', boolean - 'once' mode: Download only if file doesn't exist (offline-friendly) - 'always' mode: Check for updates on every build (previous behavior) - 'manual' mode: User manages schema files manually - Rename schemaPath to downloadPath for clarity - Store downloaded schemas in .nitro/graphql/schemas/ directory - Improve build performance by avoiding unnecessary network requests - Add comprehensive documentation with usage examples This optimizes build performance while providing flexibility for different development workflows and deployment scenarios. * fix: improve documentation formatting for schema download options * feat: add GraphQL queries and mutations for countries and users * feat: add optional GraphQL configuration for IDE support and service-specific validation * chore: implement GraphQL client and SDK with context definition
1 parent efdd4cf commit 293e07a

File tree

29 files changed

+2303
-1114
lines changed

29 files changed

+2303
-1114
lines changed

README.md

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
- 🔄 **Hot Reload**: Development mode with automatic schema and resolver updates
3131
- 📦 **Optimized Bundling**: Smart chunking and dynamic imports for production
3232
- 🌐 **Nuxt Integration**: First-class Nuxt.js support with dedicated module
33+
- 🔗 **Multi-Service Support**: Connect to multiple external GraphQL APIs alongside your main server
3334

3435
## 🎯 Used Projects
3536

@@ -1115,6 +1116,249 @@ Help us improve nitro-graphql! Pick any item and contribute:
11151116
> [!NOTE]
11161117
> Have other ideas? Open an issue to discuss!
11171118
1119+
## 🔗 Multi-Service GraphQL Support
1120+
1121+
Connect to multiple external GraphQL APIs alongside your main GraphQL server. Perfect for integrating with services like GitHub API, Shopify API, or any GraphQL endpoint.
1122+
1123+
### Configuration
1124+
1125+
```typescript
1126+
// nuxt.config.ts (for Nuxt projects)
1127+
export default defineNuxtConfig({
1128+
nitro: {
1129+
graphql: {
1130+
framework: 'graphql-yoga',
1131+
externalServices: [
1132+
{
1133+
name: 'countries',
1134+
schema: 'https://countries.trevorblades.com',
1135+
endpoint: 'https://countries.trevorblades.com',
1136+
documents: ['app/graphql/external/countries/**/*.graphql'],
1137+
headers: {
1138+
// Optional: Add custom headers
1139+
'Authorization': 'Bearer your-token'
1140+
}
1141+
},
1142+
{
1143+
name: 'github',
1144+
schema: 'https://api.github.com/graphql',
1145+
endpoint: 'https://api.github.com/graphql',
1146+
documents: ['app/graphql/external/github/**/*.graphql'],
1147+
headers: () => ({
1148+
// Dynamic headers with function
1149+
'Authorization': `Bearer ${process.env.GITHUB_TOKEN}`
1150+
})
1151+
}
1152+
]
1153+
}
1154+
}
1155+
})
1156+
```
1157+
1158+
### Schema Download & Caching (Optional)
1159+
1160+
For better performance and offline development, you can download and cache external schemas locally:
1161+
1162+
```typescript
1163+
// nuxt.config.ts
1164+
export default defineNuxtConfig({
1165+
nitro: {
1166+
graphql: {
1167+
framework: 'graphql-yoga',
1168+
externalServices: [
1169+
{
1170+
name: 'github',
1171+
schema: 'https://docs.github.com/public/schema.docs.graphql',
1172+
endpoint: 'https://api.github.com/graphql',
1173+
downloadSchema: 'once', // Download mode (see options below)
1174+
downloadPath: './schemas/github.graphql', // Optional: custom download path
1175+
headers: () => ({
1176+
'Authorization': `Bearer ${process.env.GITHUB_TOKEN}`
1177+
})
1178+
}
1179+
]
1180+
}
1181+
}
1182+
})
1183+
```
1184+
1185+
**Download Modes:**
1186+
1187+
| Mode | Behavior | Use Case |
1188+
|------|----------|----------|
1189+
| `true` or `'once'` | Download only if file doesn't exist | **Offline-friendly development** |
1190+
| `'always'` | Check for updates on every build | **Always stay up-to-date** |
1191+
| `'manual'` | Never download automatically | **Full manual control** |
1192+
| `false` | Disable schema downloading | **Always use remote** |
1193+
1194+
**Benefits:**
1195+
- **Offline Development**: Work without internet connection after initial download
1196+
- **Faster Builds**: No remote fetching on each build when using 'once' mode
1197+
- **Version Control**: Commit downloaded schemas to track API changes
1198+
- **Network Reliability**: Fallback to cached schema if remote is unavailable
1199+
1200+
**How it works:**
1201+
- **'once' mode (recommended)**: Downloads schema only if file doesn't exist, then uses cached version
1202+
- **'always' mode**: Checks for schema changes on every build using hash comparison
1203+
- **'manual' mode**: User manages schema files manually, no automatic downloading
1204+
1205+
**File locations:**
1206+
- Default: `.nitro/graphql/schemas/[serviceName].graphql`
1207+
- Custom: Use `downloadPath` option to specify your preferred location
1208+
1209+
### Usage
1210+
1211+
#### 1. Create External Service Queries
1212+
1213+
```graphql
1214+
<!-- app/graphql/external/countries/countries.graphql -->
1215+
query GetCountries {
1216+
countries {
1217+
code
1218+
name
1219+
emoji
1220+
continent {
1221+
name
1222+
}
1223+
}
1224+
}
1225+
1226+
query GetCountry($code: ID!) {
1227+
country(code: $code) {
1228+
code
1229+
name
1230+
capital
1231+
currency
1232+
}
1233+
}
1234+
```
1235+
1236+
#### 2. Use Generated SDKs
1237+
1238+
```typescript
1239+
// Import from centralized index
1240+
import { $sdk, $countriesSdk, $githubSdk } from '~/app/graphql'
1241+
1242+
// Or import directly from service folders
1243+
import { $countriesSdk } from '~/app/graphql/countries/ofetch'
1244+
1245+
// Use in components
1246+
const countries = await $countriesSdk.GetCountries()
1247+
const country = await $countriesSdk.GetCountry({ code: 'US' })
1248+
1249+
// Your main service still works
1250+
const users = await $sdk.GetUsers()
1251+
```
1252+
1253+
#### 3. Folder Structure
1254+
1255+
After configuration, your project structure becomes:
1256+
1257+
```
1258+
app/graphql/
1259+
├── index.ts # Centralized exports (auto-generated)
1260+
├── default/ # Your main GraphQL service
1261+
│ ├── ofetch.ts # Main service client
1262+
│ └── sdk.ts # Main service SDK
1263+
├── countries/ # External countries service
1264+
│ ├── ofetch.ts # Countries service client
1265+
│ └── sdk.ts # Countries service SDK
1266+
├── github/ # External GitHub service
1267+
│ ├── ofetch.ts # GitHub service client
1268+
│ └── sdk.ts # GitHub service SDK
1269+
└── external/ # Your external service queries
1270+
├── countries/
1271+
│ └── countries.graphql
1272+
└── github/
1273+
└── repositories.graphql
1274+
```
1275+
1276+
#### 4. TypeScript Support
1277+
1278+
Each service gets its own type definitions:
1279+
1280+
```typescript
1281+
// Types are automatically generated and available
1282+
import type { GetCountriesQuery } from '#graphql/client/countries'
1283+
import type { GetUsersQuery } from '#graphql/client'
1284+
1285+
const handleCountries = (countries: GetCountriesQuery) => {
1286+
// Fully typed countries data
1287+
}
1288+
```
1289+
1290+
### Service Configuration Options
1291+
1292+
| Option | Type | Required | Description |
1293+
|--------|------|----------|-------------|
1294+
| `name` | `string` || Unique service name (used for folder/file names) |
1295+
| `schema` | `string` \| `string[]` || GraphQL schema URL or file path |
1296+
| `endpoint` | `string` || GraphQL endpoint URL for queries |
1297+
| `documents` | `string[]` || Glob patterns for GraphQL query files |
1298+
| `headers` | `Record<string, string>` \| `() => Record<string, string>` || Custom headers for schema introspection and queries |
1299+
| `codegen.client` | `CodegenClientConfig` || Custom codegen configuration for client types |
1300+
| `codegen.clientSDK` | `GenericSdkConfig` || Custom codegen configuration for SDK generation |
1301+
1302+
## 🛠️ GraphQL Config (Optional but Recommended)
1303+
1304+
To enable GraphQL language features in your IDE (autocompletion, validation, go-to definition), create a `graphql.config.ts` file in your project root:
1305+
1306+
### For Single Service (Main GraphQL Server Only)
1307+
1308+
```typescript
1309+
// graphql.config.ts
1310+
import type { IGraphQLConfig } from 'graphql-config'
1311+
1312+
export default <IGraphQLConfig> {
1313+
schema: ['./.nuxt/graphql/schema.graphql'],
1314+
documents: ['./app/graphql/**/*.{graphql,js,ts,jsx,tsx}'],
1315+
exclude: ['./app/graphql/external/**/*'] // Exclude external service documents
1316+
}
1317+
```
1318+
1319+
### For Multi-Service Setup
1320+
1321+
```typescript
1322+
// graphql.config.ts
1323+
import type { IGraphQLConfig } from 'graphql-config'
1324+
1325+
export default <IGraphQLConfig> {
1326+
projects: {
1327+
// Main GraphQL server
1328+
default: {
1329+
schema: ['./.nuxt/graphql/schema.graphql'],
1330+
documents: ['./app/graphql/default/**/*.{graphql,js,ts,jsx,tsx}']
1331+
},
1332+
// External services
1333+
github: {
1334+
schema: [
1335+
// Use downloaded schema if available, otherwise use remote
1336+
'./.nuxt/graphql/schemas/github.graphql',
1337+
// Fallback to remote if local doesn't exist
1338+
'https://docs.github.com/public/schema.docs.graphql'
1339+
],
1340+
documents: ['./app/graphql/external/github/**/*.graphql']
1341+
},
1342+
countries: {
1343+
schema: ['./.nuxt/graphql/schemas/countries.graphql'],
1344+
documents: ['./app/graphql/external/countries/**/*.graphql']
1345+
}
1346+
}
1347+
}
1348+
```
1349+
1350+
### Schema Paths for Different Download Modes
1351+
1352+
- **Downloaded schemas**: `./.nuxt/graphql/schemas/[serviceName].graphql`
1353+
- **Custom download path**: Use your `downloadPath` configuration
1354+
- **Remote fallback**: Include remote URL as second option
1355+
1356+
This configuration enables:
1357+
- 🎯 **Service-specific validation**: Each GraphQL service gets its own validation rules
1358+
- 🚀 **IDE autocompletion**: Full IntelliSense for queries and mutations
1359+
-**Real-time validation**: Catch GraphQL errors while typing
1360+
- 🔍 **Go-to definition**: Navigate to type definitions across services
1361+
11181362
## 🛠️ VS Code Extensions
11191363

11201364
For the best development experience with GraphQL, install these recommended VS Code extensions:

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
"@graphql-tools/load-files": "catalog:",
8484
"@graphql-tools/merge": "catalog:",
8585
"@graphql-tools/schema": "catalog:",
86+
"@graphql-tools/url-loader": "catalog:",
8687
"@graphql-tools/utils": "catalog:",
8788
"chokidar": "catalog:",
8889
"consola": "catalog:",
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
query GetCountries {
2+
countries {
3+
code
4+
name
5+
emoji
6+
continent {
7+
name
8+
}
9+
}
10+
}
11+
12+
query GetCountry($code: ID!) {
13+
country (code: $code) {
14+
code
15+
name
16+
emoji
17+
phone
18+
capital
19+
currency
20+
native
21+
continent {
22+
name
23+
code
24+
}
25+
languages {
26+
name
27+
code
28+
}
29+
}
30+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// This file is auto-generated once by nitro-graphql for quick start
2+
// You can modify this file according to your needs
3+
import type { Requester, Sdk } from './sdk'
4+
import { getSdk } from './sdk'
5+
6+
export function createCountriesGraphQLClient(endpoint: string = 'https://countries.trevorblades.com'): Requester {
7+
return async <R>(doc: string, vars?: any): Promise<R> => {
8+
const headers = import.meta.server ? useRequestHeaders() : undefined
9+
10+
const result = await $fetch(endpoint, {
11+
method: 'POST',
12+
body: { query: doc, variables: vars },
13+
headers: {
14+
'Content-Type': 'application/json',
15+
...headers,
16+
},
17+
})
18+
19+
return result as R
20+
}
21+
}
22+
23+
export const $countriesSdk: Sdk = getSdk(createCountriesGraphQLClient())
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// THIS FILE IS GENERATED, DO NOT EDIT!
2+
/* eslint-disable eslint-comments/no-unlimited-disable */
3+
/* tslint:disable */
4+
/* eslint-disable */
5+
/* prettier-ignore */
6+
import type * as Types from '#graphql/client/countries';
7+
8+
import type { ExecutionResult } from 'graphql';
9+
10+
export const GetCountriesDocument = /*#__PURE__*/ `
11+
query GetCountries {
12+
countries {
13+
code
14+
name
15+
emoji
16+
continent {
17+
name
18+
}
19+
}
20+
}
21+
`;
22+
export const GetCountryDocument = /*#__PURE__*/ `
23+
query GetCountry($code: ID!) {
24+
country(code: $code) {
25+
code
26+
name
27+
emoji
28+
phone
29+
capital
30+
currency
31+
native
32+
continent {
33+
name
34+
code
35+
}
36+
languages {
37+
name
38+
code
39+
}
40+
}
41+
}
42+
`;
43+
export type Requester<C = {}, E = unknown> = <R, V>(doc: string, vars?: V, options?: C) => Promise<ExecutionResult<R, E>> | AsyncIterable<ExecutionResult<R, E>>
44+
export function getSdk<C, E>(requester: Requester<C, E>) {
45+
return {
46+
GetCountries(variables?: Types.GetCountriesQueryVariables, options?: C): Promise<ExecutionResult<Types.GetCountriesQuery, E>> {
47+
return requester<Types.GetCountriesQuery, Types.GetCountriesQueryVariables>(GetCountriesDocument, variables, options) as Promise<ExecutionResult<Types.GetCountriesQuery, E>>;
48+
},
49+
GetCountry(variables: Types.GetCountryQueryVariables, options?: C): Promise<ExecutionResult<Types.GetCountryQuery, E>> {
50+
return requester<Types.GetCountryQuery, Types.GetCountryQueryVariables>(GetCountryDocument, variables, options) as Promise<ExecutionResult<Types.GetCountryQuery, E>>;
51+
}
52+
};
53+
}
54+
export type Sdk = ReturnType<typeof getSdk>;
File renamed without changes.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Export external GraphQL services (auto-generated for existing services)
2+
// When you add new external services, don't forget to add their exports here:
3+
// export * from './yourServiceName/ofetch'
4+
export * from './countries/ofetch'
5+
6+
// This file is auto-generated once by nitro-graphql for quick start
7+
// You can modify this file according to your needs
8+
//
9+
// Export your main GraphQL service (auto-generated)
10+
export * from './default/ofetch'

0 commit comments

Comments
 (0)