├── README.md ├── index.js └── package.json /README.md: -------------------------------------------------------------------------------- 1 | # postgraphile-apollo-server 2 | 3 | This module performs some of the boilerplate for using PostGraphile with Apollo 4 | Server. 5 | 6 | ## Usage 7 | 8 | ```js 9 | const pg = require("pg"); 10 | const { ApolloServer } = require("apollo-server"); 11 | const { makeSchemaAndPlugin } = require("postgraphile-apollo-server"); 12 | 13 | const pgPool = new pg.Pool({ 14 | connectionString: process.env.DATABASE_URL 15 | }); 16 | 17 | async function main() { 18 | const { schema, plugin } = await makeSchemaAndPlugin( 19 | pgPool, 20 | 'public', // PostgreSQL schema to use 21 | { 22 | // PostGraphile options, see: 23 | // https://www.graphile.org/postgraphile/usage-library/ 24 | } 25 | ); 26 | 27 | const server = new ApolloServer({ 28 | schema, 29 | plugins: [plugin] 30 | }); 31 | 32 | const { url } = await server.listen(); 33 | console.log(`🚀 Server ready at ${url}`); 34 | } 35 | 36 | main().catch(e => { 37 | console.error(e); 38 | process.exit(1); 39 | }); 40 | ``` 41 | 42 | ## Limitations 43 | 44 | Not all PostGraphile library options are supported at this time; for example 45 | `watchPg` is not. 46 | 47 | ## Example 48 | 49 | https://github.com/graphile/postgraphile-example-apollo-server 50 | 51 | ## Changelog 52 | 53 | - v0.1.0 - initial release 54 | - v0.1.1 - fix headers after Apollo Server breaking change (thanks @miguelocarvajal) 55 | 56 | ## TODO: 57 | 58 | - [ ] Improve this README! 59 | - [ ] Compile a list of the unsupported PostGraphile library options 60 | - [ ] Don't require a `pgPool` to be passed - allow a connection string instead 61 | - [ ] Add tests 62 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const { 2 | createPostGraphileSchema, 3 | withPostGraphileContext 4 | } = require("postgraphile"); 5 | 6 | /* 7 | * PostGraphile requires an authenticated pgClient to be on `context` when it 8 | * runs, and for that client to be released back to the pool when the request 9 | * completes/fails. In PostGraphile we wrap the GraphQL query with 10 | * `withPostGraphileContext to ensure that this is handled. 11 | * 12 | * Apollo Server has a `context` callback which can be used to generate the 13 | * context, but unfortunately it does not have a `releaseContext` method to 14 | * clear up the context once the request is done. We cannot provision the 15 | * pgClient in `context` itself (as would be cleanest) because certain error 16 | * conditions within Apollo Server would mean that we never get a chance to 17 | * release it. 18 | * 19 | * Instead we must use the lifecycle-hooks functionality in the latest Apollo 20 | * Server to write to the context when the request starts, and clear the 21 | * context when the result (success or error) will be sent. 22 | */ 23 | 24 | exports.makeSchemaAndPlugin = async (pgPool, dbSchema, postGraphileOptions) => { 25 | if (!pgPool || typeof pgPool !== "object") { 26 | throw new Error("The first argument must be a pgPool instance"); 27 | } 28 | 29 | // See https://www.graphile.org/postgraphile/usage-schema/ for schema-only usage guidance 30 | const { 31 | pgSettings: pgSettingsGenerator, 32 | additionalGraphQLContextFromRequest, 33 | jwtSecret 34 | } = postGraphileOptions; 35 | 36 | function makePostgraphileApolloRequestHooks() { 37 | let finished; 38 | return { 39 | /* 40 | * Since `requestDidStart` itself is synchronous, we must hijack an 41 | * asynchronous callback in order to set up our context. 42 | */ 43 | async didResolveOperation(requestContext) { 44 | const { 45 | context: graphqlContext, 46 | request: graphqlRequest 47 | } = requestContext; 48 | 49 | /* 50 | * Get access to the original HTTP request to determine the JWT and 51 | * also perform anything needed for pgSettings support. (Actually this 52 | * is a subset of the original HTTP request according to the Apollo 53 | * Server typings, it only contains "headers"?) 54 | */ 55 | const { http: req } = graphqlRequest; 56 | 57 | /* 58 | * The below code implements similar logic to this area of 59 | * PostGraphile: 60 | * 61 | * https://github.com/graphile/postgraphile/blob/ff620cac86f56b1cd58d6a260e51237c19df3017/src/postgraphile/http/createPostGraphileHttpRequestHandler.ts#L114-L131 62 | */ 63 | 64 | // Extract the JWT if present: 65 | const jwtToken = jwtSecret ? getJwtToken(req) : null; 66 | 67 | // Extract additional context 68 | const additionalContext = 69 | typeof additionalGraphQLContextFromRequest === "function" 70 | ? await additionalGraphQLContextFromRequest(req /*, res */) 71 | : {}; 72 | 73 | // Perform the `pgSettings` callback, if appropriate 74 | const pgSettings = 75 | typeof pgSettingsGenerator === "function" 76 | ? await pgSettingsGenerator(req) 77 | : pgSettingsGenerator; 78 | 79 | // Finally add our required properties to the context 80 | const withContextOptions = { 81 | ...postGraphileOptions, 82 | pgSettings, 83 | pgPool, 84 | jwtToken 85 | }; 86 | await new Promise((resolve, reject) => { 87 | withPostGraphileContext( 88 | withContextOptions, 89 | postgrapileContext => 90 | new Promise(releaseContext => { 91 | // Jesse, an Apollo Server developer, told me to do this 😜 92 | Object.assign( 93 | graphqlContext, 94 | additionalGraphQLContextFromRequest, 95 | postgrapileContext 96 | ); 97 | 98 | /* 99 | * Don't resolve (don't release the pgClient on context) until 100 | * the request is complete. 101 | */ 102 | finished = releaseContext; 103 | 104 | // The context is now ready to be used. 105 | resolve(); 106 | }) 107 | ).catch(e => { 108 | console.error("Error occurred creating context!"); 109 | console.error(e); 110 | // Release context 111 | if (finished) { 112 | finished(); 113 | finished = null; 114 | } 115 | 116 | reject(e); 117 | }); 118 | }); 119 | }, 120 | willSendResponse(context) { 121 | // Release the context; 122 | if (finished) { 123 | finished(); 124 | finished = null; 125 | } 126 | } 127 | }; 128 | } 129 | 130 | const schema = await createPostGraphileSchema( 131 | pgPool, 132 | dbSchema, 133 | postGraphileOptions 134 | ); 135 | 136 | const plugin = { 137 | requestDidStart() { 138 | return makePostgraphileApolloRequestHooks(); 139 | } 140 | }; 141 | 142 | return { 143 | schema, 144 | plugin 145 | }; 146 | }; 147 | 148 | function httpError(status, message) { 149 | const err = new Error(message); 150 | err.status = status; 151 | return err; 152 | } 153 | 154 | function createBadAuthorizationHeaderError() { 155 | return httpError( 156 | 400, 157 | "Authorization header is not of the correct bearer scheme format." 158 | ); 159 | } 160 | 161 | const authorizationBearerRex = /^\s*bearer\s+([a-z0-9\-._~+/]+=*)\s*$/i; 162 | function getJwtToken(request) { 163 | const authorization = request.headers.get("authorization"); 164 | if (Array.isArray(authorization)) throw createBadAuthorizationHeaderError(); 165 | 166 | // If there was no authorization header, just return null. 167 | if (authorization == null) return null; 168 | 169 | const match = authorizationBearerRex.exec(authorization); 170 | 171 | // If we did not match the authorization header with our expected format, 172 | // throw a 400 error. 173 | if (!match) throw createBadAuthorizationHeaderError(); 174 | 175 | // Return the token from our match. 176 | return match[1]; 177 | } 178 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "postgraphile-apollo-server", 3 | "version": "0.1.1", 4 | "description": "Helper for using PostGraphile with Apollo Server", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/graphile/postgraphile-apollo-server.git" 12 | }, 13 | "keywords": [ 14 | "postgraphile", 15 | "graphile", 16 | "apollo-server", 17 | "apollo", 18 | "server", 19 | "graphql" 20 | ], 21 | "author": "Benjie Gillam ", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/graphile/postgraphile-apollo-server/issues" 25 | }, 26 | "peerDependencies": { 27 | "postgraphile": "4.x" 28 | }, 29 | "homepage": "https://github.com/graphile/postgraphile-apollo-server#readme", 30 | "files": [ 31 | "index.js" 32 | ] 33 | } 34 | --------------------------------------------------------------------------------