Skip to content

Commit 46ab2c3

Browse files
authored
Merge pull request #4 from Niicck/serverless
feat(scripts) - enable automated AWS deployments using serverless.js
2 parents a40b56e + d0cfc3b commit 46ab2c3

File tree

12 files changed

+213
-29
lines changed

12 files changed

+213
-29
lines changed

.env.template

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,10 @@ export POSTGRES_USER="forum_example_postgraphile"
22
export POSTGRES_PASSWORD="xyz"
33
export POSTGRES_DB="forum_example_postgraphile"
44
export DATABASE_SCHEMAS="forum_example"
5-
export DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost/${POSTGRES_DB}"
5+
export DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:5432/${POSTGRES_DB}"
66
export JWT_SECRET="2c045e2eb2ec46488530"
7+
export JWT_PG_TYPE_IDENTIFIER="forum_example.jwt_token"
8+
9+
export AWS_SERVICE_NAME="my-postgraphile-lambda"
10+
export AWS_REGION="us-east-1"
11+
export AWS_STAGE="dev"

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@
44
/dist
55
/test/template.yml
66
/lambda.zip
7+
/.serverless/
8+
.DS_Store

README.md

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,9 @@ We use the following tools:
3535
during the build and write the results to a cache file to be included in the
3636
bundle; then when the Lambda service starts up it can read from the cache
3737
rather than introspecting the database again.
38+
- serverless.js (optional) - for automated AWS deployments.
3839

39-
## Building lambda.zip
40+
## Setup
4041

4142
First clone this repository locally, and install dependencies:
4243

@@ -50,18 +51,29 @@ Next, set up a `.env` file matching your environment:
5051
cp .env.template .env
5152
```
5253

53-
And modify the `src/postgraphileOptions.js` file to your taste.
54+
And modify the `src/postgraphileOptions.js` and `serverless.yml` files to your taste.
5455

55-
Finally run:
56+
## Automatic Deployment with Serverless.js
57+
58+
#### Deployment Prerequisites
59+
60+
- [serverless](https://serverless.com/framework/docs/providers/aws/guide/installation/) - `yarn global add serverless`
61+
62+
After configuring your `.env` file, ~/.aws/credentials, and postgraphileOptions.js, you can deploy to AWS using serverless.js by running:
5663

5764
```
58-
yarn build
65+
yarn deploy
5966
```
6067

61-
This will result in a `lambda.zip` file that you can upload to Amazon Lambda.
68+
## Setting up a Lambda endpoint manually
69+
70+
If you prefer not to use the serverless.js framework, you can also deploy your lambda function manually.
71+
72+
Note 1: Change your process.env.AWS_STAGE_NAME to "/default" to match the default stage name for manually deployed API Gateways.
6273

63-
## Setting up a Lambda endpoint
74+
Note 2: CORS is enabled by default. Remove cors() middleware in `/src/index.js` if you would prefer disabled cors.
6475

76+
0. Run `yarn build` to create `lambda.zip` file that you can upload to Amazon Lambda.
6577
1. Visit https://console.aws.amazon.com/lambda/home and click 'Create function'
6678
2. Select "Author from scratch" and give your function a name, select the most recent Node.js release (at least 8.10+), create (or select) a role (I granted "Simple microservice permissions")
6779
3. Click "Create function" and wait about 15 seconds; you should be greeted with a "Congratulations" message.
@@ -171,7 +183,7 @@ Do the same as for the test, but instead of running `yarn test` at the end, inst
171183
yarn sam
172184
```
173185

174-
This will set up a local GraphQL endpoint at http://127.0.0.1:3000/
186+
This will set up a local GraphQL endpoint at http://127.0.0.1:3000/graphql
175187

176188
You can then use a GraphQL client such as Altair or GraphQL Playground to issue requests.
177189

package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,19 @@
77
"phase4": ". ./.env && scripts/test",
88
"build": ". ./.env && scripts/build && scripts/generate-cache && scripts/bundle",
99
"sam": ". ./.env && scripts/build && scripts/generate-cache && scripts/bundle && scripts/sam",
10-
"test": ". ./.env && scripts/build && scripts/generate-cache && scripts/bundle && scripts/test"
10+
"test": ". ./.env && scripts/build && scripts/generate-cache && scripts/bundle && scripts/test",
11+
"deploy": "scripts/serverless"
1112
},
1213
"dependencies": {
13-
"aws-serverless-express": "3.1.3",
14+
"aws-serverless-express": "^3.3.5",
1415
"cors": "^2.8.5",
1516
"pg": "7.4.1",
1617
"postgraphile": "v4.2",
1718
"postgraphile-core": "^4.2.0"
1819
},
1920
"devDependencies": {
21+
"aws-sdk": "^2.389.0",
22+
"serverless-content-encoding": "^1.1.0",
2023
"webpack": "4.17.2",
2124
"webpack-cli": "3.1.0"
2225
}

scripts/createS3Bucket.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
const aws = require('aws-sdk');
2+
const s3 = new aws.S3();
3+
4+
const bucketName = process.env.AWS_SERVICE_NAME;
5+
6+
async function main() {
7+
const { Buckets } = await s3.listBuckets().promise();
8+
9+
if (Buckets.find(bucket => bucket.Name === bucketName)) {
10+
console.log(`Bucket "${bucketName}" already exists.`);
11+
} else {
12+
const bucketParams = {
13+
Bucket: bucketName
14+
};
15+
16+
await s3.createBucket(bucketParams).promise();
17+
console.log(`Bucket "${bucketName}" created.`);
18+
}
19+
};
20+
21+
main().catch(err => {
22+
console.log('Error', err);
23+
process.exit(1);
24+
});

scripts/serverless

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#!/bin/bash
2+
set -e
3+
CURRENT_DIR=`dirname $BASH_SOURCE`
4+
5+
# Source env variables
6+
echo "Sourcing environment"
7+
if [ -x .env ]; then
8+
. ./.env
9+
echo "... done"
10+
else
11+
echo "... no executable .env file found"
12+
fi
13+
14+
if [ "$DATABASE_URL" = "" ]; then
15+
echo "No DATABASE_URL envvar found; cannot continue."
16+
exit 1
17+
fi
18+
19+
rm -rf $CURRENT_DIR/../.serverless
20+
21+
echo "Phase 1: Create serverless package"
22+
sls package
23+
24+
echo "Phase 2: Build webpack bundle and add it to serverless package"
25+
scripts/build
26+
scripts/generate-cache
27+
scripts/bundle
28+
mv $CURRENT_DIR/../lambda.zip $CURRENT_DIR/../.serverless/graphql.zip
29+
30+
# Update SHA hash for modified function zip file
31+
SHA=$(openssl dgst -sha256 -binary $CURRENT_DIR/../.serverless/graphql.zip | openssl enc -base64 | sed -e "s#/#\\\/#g")
32+
if [ $(uname -s) == 'Darwin' ]; then
33+
# OSX sed syntax is slightly different
34+
sed -i '' -e "s/\"CodeSha256\": \".*\"/\"CodeSha256\": \"${SHA}\"/g" $CURRENT_DIR/../.serverless/*.json
35+
else
36+
sed -i "s/\"CodeSha256\": \".*\"/\"CodeSha256\": \"${SHA}\"/g" $CURRENT_DIR/../.serverless/*.json
37+
fi
38+
39+
echo "Phase 3: Create S3 Bucket for serverless function"
40+
node scripts/createS3Bucket.js
41+
42+
echo "Phase 4: Deploy package to AWS"
43+
sls deploy -p .serverless
44+
45+
echo "Complete"

serverless.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
service: ${env:AWS_SERVICE_NAME}
2+
3+
plugins:
4+
- serverless-content-encoding
5+
6+
custom:
7+
contentEncoding:
8+
minimumCompressionSize: 0
9+
10+
package:
11+
individually: true
12+
13+
provider:
14+
name: aws
15+
runtime: nodejs8.10
16+
deploymentBucket:
17+
name: ${env:AWS_SERVICE_NAME}
18+
region: ${env:AWS_REGION}
19+
stage: ${env:AWS_STAGE}
20+
21+
functions:
22+
graphql:
23+
handler: index.handler # Actual handler substituted in during deploy script
24+
events:
25+
- http:
26+
method: post
27+
path: graphql
28+
cors: true
29+
integration: lambda-proxy
30+
environment:
31+
DATABASE_URL: ${env:DATABASE_URL}
32+
DATABASE_SCHEMAS: ${env:DATABASE_SCHEMAS}

src/index.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
const awsServerlessExpress = require('aws-serverless-express');
2-
const cors = require('cors');
32
const { postgraphile } = require('postgraphile');
43
const { options } = require('./postgraphileOptions');
54
const combineMiddlewares = require('./combineMiddlewares');
5+
const cors = require('cors');
66

77
const schemas = process.env.DATABASE_SCHEMAS
88
? process.env.DATABASE_SCHEMAS.split(',')
@@ -14,10 +14,10 @@ const app = combineMiddlewares([
1414
*
1515
* This is typically useful for augmenting the request before it goes to PostGraphile.
1616
*/
17-
17+
1818
// CORS middleware to permit cross-site API requests. Configure to taste
1919
cors(),
20-
20+
2121
// Determines the effective URL we are at if `absoluteRoutes` is set
2222
(req, res, next) => {
2323
if (options.absoluteRoutes) {
@@ -34,7 +34,6 @@ const app = combineMiddlewares([
3434
next();
3535
},
3636
postgraphile(process.env.DATABASE_URL, schemas, {
37-
graphqlRoute: '/',
3837
...options,
3938
readCache: `${__dirname}/postgraphile.cache`,
4039
}),

src/postgraphileOptions.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,18 @@ exports.options = {
22
dynamicJson: true,
33
cors: true,
44
graphiql: false,
5-
externalUrlBase: '/default',
5+
graphqlRoute: '/graphql',
6+
externalUrlBase: `/${process.env.AWS_STAGE}`,
67

78
// If consuming JWT:
89
jwtSecret: process.env.JWT_SECRET || String(Math.random()),
910
// If generating JWT:
10-
jwtPgTypeIdentifier: 'forum_example.jwt_token',
11+
jwtPgTypeIdentifier: process.env.JWT_PG_TYPE_IDENTIFIER,
1112

1213
/* If you want to enable GraphiQL, you must use `externalUrlBase` so PostGraphile
1314
* knows where to tell the browser to find the assets. Doing this is
1415
* strongly discouraged, you should use an external GraphQL client instead.
1516
16-
externalUrlBase: '/default',
1717
graphiql: true,
1818
enhanceGraphiql: true,
1919
graphqlRoute: '/',

test/make-template-yml.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ Resources:
2323
Api:
2424
Type: Api
2525
Properties:
26-
Path: /
26+
Path: /graphql
2727
Method: post
2828
OptionsRoute:
2929
Type: Api
3030
Properties:
31-
Path: /
31+
Path: /graphql
3232
Method: options
3333
Favicon:
3434
Type: Api

0 commit comments

Comments
 (0)