Skip to content

Commit 670a15a

Browse files
authored
feat(scripts) - enable automated AWS deployments using serverless.js (#1)
* use serverless.js framework to deploy lambda to AWS * clean up dependencies and documentation
1 parent a40b56e commit 670a15a

File tree

13 files changed

+211
-45
lines changed

13 files changed

+211
-45
lines changed

.DS_Store

6 KB
Binary file not shown.

.env.template

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,9 @@ 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+
8+
export AWS_SERVICE_NAME="my-postgraphile-lambda"
9+
export AWS_REGION="us-east-1"
10+
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: 15 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,25 @@ 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
6269

63-
## Setting up a Lambda endpoint
70+
If you prefer not to use the serverless.js framewwork, you can also deploy your lambda function manually.
6471

72+
0. Run `yarn build` to create `lambda.zip` file that you can upload to Amazon Lambda.
6573
1. Visit https://console.aws.amazon.com/lambda/home and click 'Create function'
6674
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")
6775
3. Click "Create function" and wait about 15 seconds; you should be greeted with a "Congratulations" message.
@@ -171,7 +179,7 @@ Do the same as for the test, but instead of running `yarn test` at the end, inst
171179
yarn sam
172180
```
173181

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

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

package.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,18 @@
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-
"cors": "^2.8.5",
14+
"aws-serverless-express": "^3.3.5",
1515
"pg": "7.4.1",
1616
"postgraphile": "v4.2",
1717
"postgraphile-core": "^4.2.0"
1818
},
1919
"devDependencies": {
20+
"aws-sdk": "^2.389.0",
21+
"serverless-content-encoding": "^1.1.0",
2022
"webpack": "4.17.2",
2123
"webpack-cli": "3.1.0"
2224
}

scripts/createS3Bucket.js

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

scripts/serverless

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#!/bin/bash
2+
CURRENT_DIR=`dirname $BASH_SOURCE`
3+
4+
# Source env variables
5+
echo "Sourcing environment"
6+
if [ -x .env ]; then
7+
. ./.env
8+
echo "... done"
9+
else
10+
echo "... no .env file found"
11+
fi
12+
13+
if [ "$DATABASE_URL" = "" ]; then
14+
echo "No DATABASE_URL envvar found; cannot continue."
15+
exit 1
16+
fi
17+
18+
rm -rf .serverless
19+
20+
echo "Phase 1: Create serverless package"
21+
sls package
22+
23+
echo "Phase 2: Build webpack bundle and add it to serverless package"
24+
scripts/build
25+
scripts/generate-cache
26+
scripts/bundle
27+
mv $CURRENT_DIR/../lambda.zip $CURRENT_DIR/../.serverless/graphql.zip
28+
29+
# Update SHA hash for modified function zip file
30+
SHA=$(openssl dgst -sha256 -binary $CURRENT_DIR/../.serverless/graphql.zip | openssl enc -base64 | sed -e "s#/#\\\/#g")
31+
if [ $(uname -s) == 'Darwin' ]; then
32+
# OSX sed syntax is slightly different
33+
sed -i '' -e "s/\"CodeSha256\": \".*\"/\"CodeSha256\": \"${SHA}\"/g" $CURRENT_DIR/../.serverless/*.json
34+
else
35+
sed -i "s/\"CodeSha256\": \".*\"/\"CodeSha256\": \"${SHA}\"/g" $CURRENT_DIR/../.serverless/*.json
36+
fi
37+
38+
echo "Phase 3: Create S3 Bucket for serverless function"
39+
node scripts/createS3Bucket.js
40+
41+
echo "Phase 4: Deploy package to AWS"
42+
sls deploy -p .serverless
43+
44+
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: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
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');
@@ -14,10 +13,7 @@ const app = combineMiddlewares([
1413
*
1514
* This is typically useful for augmenting the request before it goes to PostGraphile.
1615
*/
17-
18-
// CORS middleware to permit cross-site API requests. Configure to taste
19-
cors(),
20-
16+
2117
// Determines the effective URL we are at if `absoluteRoutes` is set
2218
(req, res, next) => {
2319
if (options.absoluteRoutes) {
@@ -34,7 +30,6 @@ const app = combineMiddlewares([
3430
next();
3531
},
3632
postgraphile(process.env.DATABASE_URL, schemas, {
37-
graphqlRoute: '/',
3833
...options,
3934
readCache: `${__dirname}/postgraphile.cache`,
4035
}),

src/postgraphileOptions.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ exports.options = {
22
dynamicJson: true,
33
cors: true,
44
graphiql: false,
5-
externalUrlBase: '/default',
5+
externalUrlBase: '',
6+
graphqlRoute: '/graphql',
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: 'floods.jwt_token',
12+
pgDefaultRole: 'floods_anonymous',
1113

1214
/* If you want to enable GraphiQL, you must use `externalUrlBase` so PostGraphile
1315
* knows where to tell the browser to find the assets. Doing this is

0 commit comments

Comments
 (0)