Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wip: complex example showcasing multiple plugins #624

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions examples/complex/.env.sample
@@ -0,0 +1,3 @@
AUTH0_CLIENT_ID=
AUTH0_AUDIENCE=http://localhost:3000/graphql
AUTH0_DOMAIN=dream-watch.eu.auth0.com
1 change: 1 addition & 0 deletions examples/complex/README.md
@@ -0,0 +1 @@
# TODO
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO!

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah it is not done yet

29 changes: 29 additions & 0 deletions examples/complex/package.json
@@ -0,0 +1,29 @@
{
"name": "@envelop-examples/example",
"private": true,
"version": "1.0.0",
"main": "index.js",
"author": "Laurin Quast",
"license": "MIT",
"dependencies": {
"@envelop/auth0": "1.0.0",
"@envelop/core": "1.0.4-alpha-5276a31.0",
"@graphql-tools/schema": "8.1.2",
"dotenv": "10.0.0",
"envalid": "7.2.1",
"fastify": "3.20.2",
"graphql": "experimental-stream-defer",
"graphql-helix": "1.7.0"
},
"devDependencies": {
"@envelop/apollo-tracing": "1.0.3",
"@types/node": "15.6.1",
"ts-node": "10.0.0",
"ts-node-dev": "1.1.8",
"typescript": "4.3.5"
},
"scripts": {
"start": "ts-node -r dotenv/config src/index.ts",
"start:dev": "ts-node-dev -r dotenv/config src/index.ts"
}
}
124 changes: 124 additions & 0 deletions examples/complex/src/index.ts
@@ -0,0 +1,124 @@
/* eslint-disable no-console */
import fastify from 'fastify';
import { getGraphQLParameters, processRequest, renderGraphiQL, shouldRenderGraphiQL } from 'graphql-helix';
import { envelop, useSchema } from '@envelop/core';
import { useAuth0 } from '@envelop/auth0';
import * as envalid from 'envalid';
import { handleHelixResult } from './util';
import { schema } from './schema';

const env = envalid.cleanEnv(process.env, {
AUTH0_CLIENT_ID: envalid.str(),
AUTH0_AUDIENCE: envalid.str(),
AUTH0_DOMAIN: envalid.str(),
});

const auth0Config = {
domain: env.AUTH0_DOMAIN,
audience: env.AUTH0_AUDIENCE,
clientId: env.AUTH0_CLIENT_ID,
};

const getEnveloped = envelop({
plugins: [
useSchema(schema),
useAuth0({
domain: auth0Config.domain,
audience: auth0Config.audience,
preventUnauthenticatedAccess: false, // If you need to have unauthenticated parts on your schema, make sure to disable that by setting it to `false` and the check it in your resolvers.
extendContextField: 'auth0', // The name of the field injected to your `context`
tokenType: 'Bearer', // Type of token to expect in the header
}),
],
});

const app = fastify();

app.route({
method: 'GET',
url: '/',
async handler(req, res) {
res.header('Content-Type', 'text/html; charset=UTF-8');
res.send(/* HTML */ `
<!DOCTYPE html />
<html>
<head>
<script src="https://cdn.auth0.com/js/auth0-spa-js/1.12/auth0-spa-js.production.js"></script>
</head>
<body>
<script>
createAuth0Client({
domain: '${auth0Config.domain}',
client_id: '${auth0Config.clientId}',
audience: '${auth0Config.audience}',
}).then(async auth0 => {
const isAuthenticated = await auth0.isAuthenticated();
await auth0.loginWithPopup();
const accessToken = await auth0.getTokenSilently();
window.document.body.innerText = accessToken;
});
</script>
</body>
</html>
`);
},
});

const graphiqlContent = /* GraphQL */ `
query isAuthenticated {
isAuthenticated
}
`;

app.route({
method: ['GET', 'POST'],
url: '/graphql',
async handler(req, res) {
const { parse, validate, contextFactory, execute, schema } = getEnveloped({ req });
const request = {
body: req.body,
headers: req.headers,
method: req.method,
query: req.query,
};

if (shouldRenderGraphiQL(request)) {
res.type('text/html');
res.send(
renderGraphiQL({
defaultQuery: graphiqlContent
.split('\n')
.slice(1)
.map(line => line.replace(' ', ''))
.join('\n'),
})
);
} else {
const request = {
body: req.body,
headers: req.headers,
method: req.method,
query: req.query,
};
const { operationName, query, variables } = getGraphQLParameters(request);

const result = await processRequest({
operationName,
query,
variables,
request,
schema,
parse,
validate,
execute,
contextFactory,
});

await handleHelixResult(result, req, res);
}
},
});

app.listen(3000, () => {
console.log(`GraphQL server is running on http://localhost:3000/graphql`);
});
24 changes: 24 additions & 0 deletions examples/complex/src/schema.ts
@@ -0,0 +1,24 @@
import { makeExecutableSchema } from '@graphql-tools/schema';

export const schema = makeExecutableSchema({
typeDefs: /* GraphQL */ `
type AuthenticationInfo {
sub: String!
}

type Query {
isAuthenticated: Boolean
authInfo: AuthenticationInfo
}
`,
resolvers: {
Query: {
isAuthenticated: (_, __, context) => {
return context.auth0 != null;
},
authInfo: (_, __, context) => {
return context.auth0;
},
},
},
});
53 changes: 53 additions & 0 deletions examples/complex/src/util.ts
@@ -0,0 +1,53 @@
import { FastifyReply, FastifyRequest } from 'fastify';
import { ProcessRequestResult } from 'graphql-helix';

export async function handleHelixResult(
result: ProcessRequestResult<any, any>,
req: FastifyRequest,
res: FastifyReply
): Promise<void> {
if (result.type === 'RESPONSE') {
res.statusCode = result.status;
res.send(result.payload);
} else if (result.type === 'MULTIPART_RESPONSE') {
res.raw.writeHead(200, {
Connection: 'keep-alive',
'Content-Type': 'multipart/mixed; boundary="-"',
'Transfer-Encoding': 'chunked',
});

req.raw.on('close', () => {
result.unsubscribe();
});

res.raw.write('---');

await result.subscribe(result => {
const chunk = Buffer.from(JSON.stringify(result), 'utf8');
const data = ['', 'Content-Type: application/json; charset=utf-8', 'Content-Length: ' + String(chunk.length), '', chunk];

if (result.hasNext) {
data.push('---');
}

res.raw.write(data.join('\r\n'));
});

res.raw.write('\r\n-----\r\n');
res.raw.end();
} else {
res.raw.writeHead(200, {
'Content-Type': 'text/event-stream',
Connection: 'keep-alive',
'Cache-Control': 'no-cache',
});

req.raw.on('close', () => {
result.unsubscribe();
});

await result.subscribe(result => {
res.raw.write(`data: ${JSON.stringify(result)}\n\n`);
});
}
}
26 changes: 26 additions & 0 deletions examples/complex/tsconfig.json
@@ -0,0 +1,26 @@
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"target": "es2020",
"lib": ["es2020"],
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"outDir": "dist",
"allowUnreachableCode": false,
"allowUnusedLabels": false,
"alwaysStrict": true,
"noImplicitAny": false,
"noImplicitReturns": true,
"noImplicitThis": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"importHelpers": true,
"skipLibCheck": true
},
"include": ["./src"],
"exclude": ["node_modules"]
}