Skip to content

Commit e7eb61e

Browse files
feat: add GraphQL subscriptions support via WebSocket (#65)
* feat: add GraphQL subscriptions support via WebSocket Implements GraphQL subscriptions for v2 using the graphql-ws library with crossws adapter. Features: - WebSocket transport with graphql-ws protocol (graphql-transport-ws) - Support for both GraphQL Yoga and Apollo Server frameworks - Built-in PubSub utility (createPubSub) for simple pub/sub patterns - crossws 0.4 compatibility via custom upgrade hook for protocol negotiation - Automatic WebSocket feature enablement when subscriptions configured Implementation: - Uses graphql-ws/use/crossws makeHooks() for protocol handling - Adds upgrade hook wrapper for Sec-WebSocket-Protocol header (crossws 0.4+) - Includes playground example with chat subscription demo Configuration: ```typescript graphql({ framework: 'graphql-yoga', subscriptions: { enabled: true, websocket: { enabled: true }, }, }) ``` Usage: ```typescript import { createPubSub, defineSubscription } from 'nitro-graphql/define' const pubsub = createPubSub<{ MESSAGE: Message }>() export const chatSubscription = defineSubscription({ messageAdded: { subscribe: () => pubsub.subscribe('MESSAGE'), resolve: (payload) => payload, }, }) ``` Peer dependency: graphql-ws (required for subscriptions) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore: lint * feat: add examples --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent b1ddb55 commit e7eb61e

28 files changed

+979
-4
lines changed

examples/subscriptions/.gitignore

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
node_modules
2+
.nitro
3+
.output
4+
.graphql
5+
dist
6+
*.log
7+
*.lock
8+
pnpm-lock.yaml
9+
package-lock.json
10+
yarn.lock
11+
bun.lock

examples/subscriptions/README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# GraphQL Subscriptions Example
2+
3+
Real-time chat with GraphQL Subscriptions over WebSocket.
4+
5+
## Setup
6+
7+
```bash
8+
pnpm install
9+
pnpm dev
10+
```
11+
12+
## Test
13+
14+
1. Open http://localhost:3000/api/graphql
15+
2. Subscribe:
16+
```graphql
17+
subscription {
18+
messageReceived {
19+
id
20+
text
21+
author
22+
}
23+
}
24+
```
25+
26+
3. In another tab, send a message:
27+
```graphql
28+
mutation {
29+
sendMessage(text: "Hello!", author: "User") {
30+
id
31+
}
32+
}
33+
```
34+
35+
## WebSocket Endpoint
36+
37+
`ws://localhost:3000/api/graphql/ws`
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import {
2+
createPubSub,
3+
defineMutation,
4+
defineQuery,
5+
defineSubscription,
6+
} from 'nitro-graphql/define'
7+
8+
interface Message {
9+
id: string
10+
text: string
11+
author: string
12+
createdAt: string
13+
}
14+
15+
const pubsub = createPubSub<{
16+
MESSAGE: Message
17+
ONLINE: number
18+
}>()
19+
20+
const messages: Message[] = []
21+
let onlineCount = 1
22+
23+
export const chatQueries = defineQuery({
24+
messages: () => messages,
25+
onlineCount: () => onlineCount,
26+
})
27+
28+
export const chatMutations = defineMutation({
29+
sendMessage: async (_, { text, author }: { text: string, author: string }) => {
30+
const message: Message = {
31+
id: crypto.randomUUID(),
32+
text,
33+
author,
34+
createdAt: new Date().toISOString(),
35+
}
36+
messages.push(message)
37+
await pubsub.publish('MESSAGE', message)
38+
return message
39+
},
40+
})
41+
42+
export const chatSubscriptions = defineSubscription({
43+
messageReceived: {
44+
subscribe: () => pubsub.subscribe('MESSAGE'),
45+
resolve: (payload: Message) => payload,
46+
},
47+
onlineCountChanged: {
48+
subscribe: () => pubsub.subscribe('ONLINE'),
49+
resolve: (payload: number) => payload,
50+
},
51+
})
52+
53+
// Simulate online count changes
54+
setInterval(() => {
55+
onlineCount = Math.floor(Math.random() * 50) + 1
56+
pubsub.publish('ONLINE', onlineCount)
57+
}, 10000)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
type Query {
2+
messages: [Message!]!
3+
onlineCount: Int!
4+
}
5+
6+
type Mutation {
7+
sendMessage(text: String!, author: String!): Message!
8+
}
9+
10+
type Subscription {
11+
messageReceived: Message!
12+
onlineCountChanged: Int!
13+
}
14+
15+
type Message {
16+
id: ID!
17+
text: String!
18+
author: String!
19+
createdAt: String!
20+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { defineConfig } from 'nitro'
2+
import graphql from 'nitro-graphql'
3+
4+
export default defineConfig({
5+
serverDir: './',
6+
modules: [
7+
graphql({
8+
framework: 'graphql-yoga',
9+
serverDir: './',
10+
subscriptions: {
11+
enabled: true,
12+
websocket: {
13+
enabled: true,
14+
},
15+
},
16+
}),
17+
],
18+
})
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name": "nitro-graphql-subscriptions-example",
3+
"type": "module",
4+
"private": true,
5+
"scripts": {
6+
"build": "nitro build",
7+
"dev": "nitro dev",
8+
"preview": "npx srvx --prod .output/"
9+
},
10+
"devDependencies": {
11+
"graphql": "^16.12.0",
12+
"graphql-ws": "^6.0.6",
13+
"graphql-yoga": "^5.18.0",
14+
"nitro": "^3.0.1-alpha.1",
15+
"nitro-graphql": "2.0.0-beta.50",
16+
"rolldown": "^1.0.0-beta.58"
17+
18+
}
19+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"extends": ["nitro/tsconfig"],
3+
"compilerOptions": {
4+
"target": "ESNext",
5+
"module": "ESNext",
6+
"moduleResolution": "Bundler",
7+
"paths": {
8+
"~/*": ["./*"],
9+
"#graphql/server": ["./.graphql/nitro-graphql-server.d.ts"],
10+
"#graphql/client": ["./.graphql/nitro-graphql-client.d.ts"]
11+
},
12+
"resolveJsonModule": true,
13+
"strict": true,
14+
"noEmit": true,
15+
"allowSyntheticDefaultImports": true,
16+
"forceConsistentCasingInFileNames": true,
17+
"skipLibCheck": true
18+
},
19+
"include": ["**/*.ts"],
20+
"exclude": ["node_modules", ".nitro", ".output"]
21+
}

package.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@
3838
"#nitro-graphql/debug-info": {
3939
"types": "./dist/nitro/virtual/stubs.d.mts",
4040
"import": "./dist/nitro/virtual/stubs.mjs"
41+
},
42+
"#nitro-graphql/pubsub": {
43+
"types": "./dist/nitro/virtual/stubs.d.mts",
44+
"import": "./dist/nitro/virtual/stubs.mjs"
4145
}
4246
},
4347
"exports": {
@@ -72,6 +76,10 @@
7276
"./nuxt": {
7377
"types": "./dist/nuxt.d.mts",
7478
"import": "./dist/nuxt.mjs"
79+
},
80+
"./pubsub": {
81+
"types": "./dist/core/pubsub/index.d.mts",
82+
"import": "./dist/core/pubsub/index.mjs"
7583
}
7684
},
7785
"module": "./dist/index.mjs",
@@ -105,6 +113,7 @@
105113
"playground:nitro": "cd playgrounds/nitro && pnpm install && pnpm dev",
106114
"playground:nuxt": "cd playgrounds/nuxt && pnpm install && pnpm dev",
107115
"playground:federation": "cd playgrounds/federation && pnpm install && pnpm dev",
116+
"playground:subscriptions": "cd playgrounds/subscriptions && pnpm install && pnpm dev",
108117
"docs:dev": "cd .docs && pnpm install && pnpm update:metadata && pnpm dev",
109118
"docs:build": "cd .docs && pnpm install && pnpm update:metadata && pnpm build",
110119
"docs:preview": "cd .docs && pnpm preview",
@@ -120,11 +129,15 @@
120129
"peerDependencies": {
121130
"@apollo/server": "^5.0.0",
122131
"graphql": "^16.12.0",
132+
"graphql-ws": "^6.0.6",
123133
"nitro": "^3.0.1-alpha.0"
124134
},
125135
"peerDependenciesMeta": {
126136
"@apollo/server": {
127137
"optional": true
138+
},
139+
"graphql-ws": {
140+
"optional": true
128141
}
129142
},
130143
"dependencies": {
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import graphql from 'nitro-graphql'
2+
import { defineNitroConfig } from 'nitro/config'
3+
4+
export default defineNitroConfig({
5+
serverDir: './',
6+
features: {
7+
websocket: true,
8+
},
9+
modules: [
10+
graphql({
11+
framework: 'graphql-yoga',
12+
subscriptions: {
13+
enabled: true,
14+
websocket: {
15+
enabled: true,
16+
},
17+
},
18+
}),
19+
],
20+
})
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "nitro-graphql-subscriptions-example",
3+
"private": true,
4+
"scripts": {
5+
"dev": "nitro dev",
6+
"build": "nitro build",
7+
"preview": "node .output/server/index.mjs"
8+
},
9+
"dependencies": {
10+
"graphql-ws": "catalog:",
11+
"nitro-graphql": "link:../.."
12+
},
13+
"devDependencies": {
14+
"nitro": "catalog:",
15+
"ws": "catalog:"
16+
}
17+
}

0 commit comments

Comments
 (0)