Skip to content

Commit

Permalink
chore(example): Implement GraphQL Subscriptions with Yoga and WebSock…
Browse files Browse the repository at this point in the history
…ets in BunJS (#3043)
  • Loading branch information
ashbuilds committed May 17, 2024
1 parent 25886fa commit 38b2c21
Show file tree
Hide file tree
Showing 4 changed files with 190 additions and 0 deletions.
71 changes: 71 additions & 0 deletions examples/bun-yoga-ws/__integration-tests__/bun.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { Server } from 'bun';
import { describe, expect, it } from 'bun:test';
import { createSchema, createYoga } from 'graphql-yoga';

describe('Bun integration', () => {
const yoga = createYoga({
schema: createSchema({
typeDefs: /* GraphQL */ `
type Query {
greetings: String
}
`,
resolvers: {
Query: {
greetings: () => 'Hello Bun!',
},
},
}),
});

let server: Server;
let url: string;
function beforeEach() {
server = Bun.serve({
fetch: yoga,
port: 3000,
});
url = `http://${server.hostname}:${server.port}${yoga.graphqlEndpoint}`;
}

function afterEach() {
server.stop();
}

it('shows GraphiQL', async () => {
beforeEach();
try {
const response = await fetch(url, {
method: 'GET',
headers: {
Accept: 'text/html',
},
});
expect(response.status).toBe(200);
expect(response.headers.get('content-type')).toBe('text/html');
const htmlContents = await response.text();
expect(htmlContents.includes('GraphiQL')).toBe(true);
} finally {
afterEach();
}
});

it('accepts a query', async () => {
beforeEach();
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: `{ greetings }`,
}),
});
const result = await response.json();
expect(result.data.greetings).toBe('Hello Bun!');
} finally {
afterEach();
}
});
});
18 changes: 18 additions & 0 deletions examples/bun-yoga-ws/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "example-bun",
"version": "0.0.0",
"license": "MIT",
"scripts": {
"check": "bun test",
"start": "bun src/index.ts"
},
"dependencies": {
"bun-types": "^1.0.0",
"graphql": "^16.6.0",
"graphql-ws": "^5.14.1",
"graphql-yoga": "^4.0.5"
},
"devDependencies": {
"@whatwg-node/fetch": "^0.9.0"
}
}
89 changes: 89 additions & 0 deletions examples/bun-yoga-ws/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import Bun from 'bun';
import { makeHandler } from 'graphql-ws/lib/use/bun';
import { createSchema, createYoga } from 'graphql-yoga';

const schema = createSchema({
typeDefs: /* GraphQL */ `
type Query {
greetings: String
}
type Subscription {
dynamicLoading(loadTime: Int!): String!
}
`,
resolvers: {
Query: {
greetings: () => 'Hello Bun!',
},
Subscription: {
dynamicLoading: {
async *subscribe(_, { loadTime }) {
let counter = 0;
const spinnerFrames = ['\u25D4', '\u25D1', '\u25D5', '\u25D0'];
while (counter < loadTime) {
await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for a second
yield { dynamicLoading: `Loading ${spinnerFrames[counter % spinnerFrames.length]}` };
counter++;
}
yield { dynamicLoading: 'Loaded \u2713' };
},
},
},
},
});

const yoga = createYoga({
schema,
graphiql: {
subscriptionsProtocol: 'WS', // use WebSockets instead of SSE
},
});

export const websocketHandler = makeHandler({
schema,
execute: args => args.rootValue.execute(args),
subscribe: args => args.rootValue.subscribe(args),
onSubscribe: async (ctx, msg) => {
const { schema, execute, subscribe, contextFactory, parse, validate } = yoga.getEnveloped({
...ctx,
req: ctx.extra.request,
socket: ctx.extra.socket,
params: msg.payload,
});

const args = {
schema,
operationName: msg.payload.operationName,
document: parse(msg.payload.query),
variableValues: msg.payload.variables,
contextValue: await contextFactory(),
rootValue: {
execute,
subscribe,
},
};

const errors = validate(args.schema, args.document);
if (errors.length) return errors;
return args;
},
});

const server: Bun.Server = Bun.serve({
fetch: (request: Request, server: Bun.Server): Promise<Response> | Response => {
// Upgrade the request to a WebSocket
if (server.upgrade(request)) {
return new Response();
}
return yoga.fetch(request, server);
},
port: 4000,
websocket: websocketHandler,
});

console.info(
`🚀 Server is running on ${new URL(
yoga.graphqlEndpoint,
`http://${server.hostname}:${server.port}`,
)}`,
);
12 changes: 12 additions & 0 deletions examples/bun-yoga-ws/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"lib": ["ESNext"],
"module": "esnext",
"moduleResolution": "node",
"target": "esnext",
// "bun-types" is the important part
"types": ["bun-types"]
},
"files": ["src/index.ts", "__integration-tests__/bun.spec.ts"]
}

0 comments on commit 38b2c21

Please sign in to comment.