Skip to content

Commit 9909e94

Browse files
initial commit
0 parents  commit 9909e94

File tree

21 files changed

+848
-0
lines changed

21 files changed

+848
-0
lines changed

.gitattributes

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/test export-ignore
2+
/infrastructure export-ignore
3+
/build expport-ignore
4+
/.gitattributes export-ignore
5+
/.gitignore export-ignore
6+
/.terraform export-ignore
7+
/README.md export-ignore
8+
/main.tf export-ignore
9+
/yarn.lock export-ignore

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules
2+
.terraform
3+
terraform.*

README.md

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Overview
2+
3+
This is an example graphql endpoint packaged as a aws lambda function deployed to an aws api gateway. All the infrastructure required is provisioned using terraform, only thing you need to use this is your AWS credentials.
4+
5+
The code is structured to hold more than one lambda function. i.e. Currently the src/ folder contains only graphql folder. You can add additional lambda functions, and modify the main.tf if needed.
6+
7+
The sample here does not use terraform backend configuration. For production, it is advisable to use a backend like S3.
8+
9+
# Configure and Create graphql endpoint using Aws Lambda and Aws api_gateway
10+
11+
## Prerequisites
12+
13+
Make sure you have installed terraform & zip installed.
14+
15+
## Clone this repo & install dependencies
16+
17+
## Set environment variables.
18+
19+
```
20+
export TF_VAR_aws_access_key=yourawsaccesskey
21+
export TF_VAR_aws_secret_key=yourawssecretkey
22+
export TF_VAR_region=awsregion
23+
24+
```
25+
## Run locally
26+
```
27+
yarn run graphql-server
28+
29+
```
30+
## Deploy infrastructure
31+
32+
If you wish to change names of variables, for example, path or name of api etc, review variables.tf. But not mandatory.
33+
34+
Run the following command.
35+
36+
```
37+
yarn run test-deploy
38+
39+
```
40+
41+
You should see the _url_ where the deployment is available. Take a note of that and use in the following steps to test it.
42+
## From your browser
43+
44+
Just browse to the _url_ path output you received in the previous step
45+
## Using Curl
46+
47+
```
48+
//_url_ is the url you received while running the script.
49+
curl -X POST _url_ -d '{"query": "{users{firstName} }" }' -H 'Content-Type: application/json'
50+
51+
```
52+
53+
## Using AWS API Test Console
54+
55+
Select _Method_ as POST in the dropdown list.
56+
57+
_Query String_, provide the following
58+
59+
```
60+
query= "{users{firstName} }"
61+
62+
```
63+
_Headers_, provide the following,
64+
```
65+
Content-Type: application/json
66+
67+
```
68+
69+
Click _Test_ button.

build/.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
*.zip
2+
node_modules

graphql_server.js

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
'use strict';
2+
3+
const app = require('./src/graphql/graphql_app');
4+
5+
// let's set the port on which the server will run
6+
app.set( 'port', 3000 );
7+
8+
// start the server
9+
app.listen(
10+
app.get('port'),
11+
() => {
12+
const port = app.get('port');
13+
console.log('GraphQL Server Running at http://localhost:' + port );
14+
}
15+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Overview
2+
If you are creating a AWS_PROXY style of integration, use this module. For example, express style integrations, use this module where everything is passed back and forth.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
2+
variable "api_name" {
3+
description = "name of the api"
4+
default = "api"
5+
}
6+
7+
variable "api_path" {
8+
description = "path of the api"
9+
default = "api"
10+
}
11+
12+
variable "lambda_arn" {
13+
description = "arn of the associated lambda function"
14+
}
15+
16+
variable "region" {
17+
description = "region"
18+
}
19+
variable "account_id" {
20+
description = "account id"
21+
}
22+
23+
variable "deploy_stage" {
24+
description = "stage name for deployment"
25+
}
26+
27+
28+
# API Gateway
29+
resource "aws_api_gateway_rest_api" "api" {
30+
name = "${var.api_name}"
31+
}
32+
33+
resource "aws_api_gateway_resource" "resource" {
34+
path_part = "${var.api_path}"
35+
parent_id = "${aws_api_gateway_rest_api.api.root_resource_id}"
36+
rest_api_id = "${aws_api_gateway_rest_api.api.id}"
37+
}
38+
39+
resource "aws_api_gateway_method" "method" {
40+
rest_api_id = "${aws_api_gateway_rest_api.api.id}"
41+
resource_id = "${aws_api_gateway_resource.resource.id}"
42+
http_method = "ANY"
43+
authorization = "NONE"
44+
}
45+
46+
resource "aws_api_gateway_integration" "integration" {
47+
rest_api_id = "${aws_api_gateway_rest_api.api.id}"
48+
resource_id = "${aws_api_gateway_resource.resource.id}"
49+
http_method = "${aws_api_gateway_method.method.http_method}"
50+
uri = "arn:aws:apigateway:${var.region}:lambda:path/2015-03-31/functions/${var.lambda_arn}/invocations"
51+
# lambda can be invoked by POST only
52+
integration_http_method = "POST"
53+
# type is AWS_PROXY as we are going to integrate with an express lambda function.
54+
type = "AWS_PROXY"
55+
}
56+
# https://docs.aws.amazon.com/apigateway/latest/developerguide/stage-variables.html?icmpid=docs_apigateway_console
57+
resource "aws_api_gateway_deployment" "deployment" {
58+
depends_on = ["aws_api_gateway_method.method","aws_api_gateway_integration.integration"]
59+
rest_api_id = "${aws_api_gateway_rest_api.api.id}"
60+
stage_name = "${var.deploy_stage}"
61+
}
62+
output "url" {
63+
value = "${aws_api_gateway_deployment.deployment.invoke_url}"
64+
}
65+
66+
# Lambda
67+
resource "aws_lambda_permission" "apigw_lambda" {
68+
statement_id = "AllowExecutionFromAPIGateway"
69+
action = "lambda:InvokeFunction"
70+
function_name = "${var.lambda_arn}"
71+
principal = "apigateway.amazonaws.com"
72+
# More: http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-control-access-using-iam-policies-to-invoke-api.html
73+
# source_arn = "arn:aws:execute-api:${var.region}:${var.account_id}:${aws_api_gateway_rest_api.api.id}/*/${aws_api_gateway_method.method.http_method}${aws_api_gateway_resource.resource.path}"
74+
# arn:aws:execute-api:region:account-id:api-id/stage/METHOD_HTTP_VERB/Resource-path
75+
source_arn = "arn:aws:execute-api:${var.region}:${var.account_id}:${aws_api_gateway_rest_api.api.id}/*/*/*"
76+
}

infrastructure/aws/lambda/main.tf

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
variable "name" {
2+
description = "The name of the lambda to create, which also defines (i) the archive name (.zip), (ii) the file name, and (iii) the function name"
3+
default = "lambda"
4+
}
5+
6+
variable "runtime" {
7+
description = "The runtime of the lambda to create"
8+
default = "nodejs6.10"
9+
}
10+
11+
variable "handler" {
12+
description = "The handler name of the lambda (a function defined in your lambda)"
13+
default = "handler"
14+
}
15+
variable "zip_file_path" {
16+
description = "path of the zip file"
17+
default = "./"
18+
}
19+
20+
module "role"{
21+
source = "./role"
22+
}
23+
24+
resource "aws_lambda_function" "lambda" {
25+
filename = "${var.zip_file_path}${var.name}.zip"
26+
function_name = "${var.name}_lambda_${var.handler}"
27+
handler = "${var.name}_lambda_${var.handler}.${var.handler}"
28+
role = "${module.role.role}"
29+
runtime = "${var.runtime}"
30+
source_code_hash = "${base64sha256(file("${var.zip_file_path}${var.name}.zip"))}"
31+
}
32+
33+
output "name" {
34+
value = "${aws_lambda_function.lambda.function_name}"
35+
}
36+
37+
output "arn" {
38+
value = "${aws_lambda_function.lambda.arn}"
39+
}
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
resource "aws_iam_role" "iam_role_for_lambda" {
2+
name = "iam_role_for_lambda"
3+
4+
assume_role_policy = <<EOF
5+
{
6+
"Version": "2012-10-17",
7+
"Statement": [
8+
{
9+
"Sid": "",
10+
"Effect": "Allow",
11+
"Principal": {
12+
"Service": "lambda.amazonaws.com"
13+
},
14+
"Action": "sts:AssumeRole"
15+
}
16+
]
17+
}
18+
EOF
19+
}
20+
21+
output "role"{
22+
value = "${aws_iam_role.iam_role_for_lambda.arn}"
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# use a backend instead of git ideally. if you are a single developer, it should be ok and remove this .gitingnore
2+
terraform.*

main.tf

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
variable "aws_access_key" {
2+
description = "AWS access key"
3+
}
4+
5+
variable "aws_secret_key" {
6+
description = "AWS secret key"
7+
}
8+
9+
variable "aws_region" {
10+
description = "AWS region"
11+
default = "us-east-1"
12+
}
13+
14+
variable "deploy_stage"{
15+
description = "Deployment Stage"
16+
}
17+
18+
provider "aws" {
19+
access_key = "${var.aws_access_key}"
20+
secret_key = "${var.aws_secret_key}"
21+
region = "${var.aws_region}"
22+
}
23+
# expose the discovered account_id etc
24+
data "aws_caller_identity" "current" {}
25+
26+
output "aws_account_id" {
27+
value = "${data.aws_caller_identity.current.account_id}"
28+
}
29+
30+
output "caller_arn" {
31+
value = "${data.aws_caller_identity.current.arn}"
32+
}
33+
34+
output "caller_user" {
35+
value = "${data.aws_caller_identity.current.user_id}"
36+
}
37+
38+
module "graphql_lambda"{
39+
source = "./infrastructure/aws/lambda"
40+
name = "graphql"
41+
zip_file_path = "./build/"
42+
}
43+
44+
module "graphql_lambda_api_gateway"{
45+
source = "./infrastructure/aws/api_gateway/aws_proxy"
46+
api_name = "${var.graphql_api_name}"
47+
api_path = "${var.graphql_api_path}"
48+
lambda_arn ="${module.graphql_lambda.arn}"
49+
region = "${var.aws_region}"
50+
account_id = "${data.aws_caller_identity.current.account_id}"
51+
deploy_stage = "${var.deploy_stage}"
52+
53+
}
54+
55+
output "url" {
56+
value = "${module.graphql_lambda_api_gateway.url}/${var.graphql_api_path}"
57+
}

package.json

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"name": "graphql-lambda-terraform",
3+
"version": "1.0.0",
4+
"description": "Example graphql lambda application with terraform IAC(infrastructure as code)",
5+
"main": "graphql_server.js",
6+
"author": "Lucid Programmer<[email protected]>",
7+
"license": "MIT",
8+
"scripts": {
9+
"graphql-server": "node graphql_server.js",
10+
"production-install": "yarn install --modules-folder=./build/node_modules --production=true",
11+
"zip-graphql": "yarn run production-install && git archive -9 -o ./build/graphql.zip HEAD:src/graphql && cd build && zip -ur ./graphql.zip . -i \"node_modules/*\"",
12+
"zip": "yarn run zip-graphql",
13+
"test-deploy-plan": "TF_VAR_deploy_stage=test yarn run zip && terraform plan -out=./infrastructure/deployment/test/terraform.tfstate",
14+
"test-deploy": "TF_VAR_deploy_stage=test yarn run test-deploy-plan && terraform apply ./infrastructure/deployment/test/terraform.tfstate"
15+
},
16+
"dependencies": {
17+
"aws-serverless-express": "^3.0.2",
18+
"body-parser": "^1.18.2",
19+
"express": "^4.16.2",
20+
"express-graphql": "^0.6.11",
21+
"graphql": "^0.11.7"
22+
},
23+
"engines": {
24+
"node": ">= 6.10.0"
25+
},
26+
"devDependencies": {
27+
"chai": "^4.1.2"
28+
}
29+
}

src/graphql/graphql_app.js

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
'use strict';
2+
3+
const express = require('express');
4+
const body_parser = require('body-parser');
5+
6+
const expressGraphQL = require('express-graphql');
7+
8+
// let's import the schema file we just created
9+
const GraphQLSchema = require('./schema');
10+
11+
const app = express();
12+
13+
app.use( body_parser.json({ limit: '50mb' }) );
14+
15+
app.use(
16+
'/',
17+
expressGraphQL( () => {
18+
return {
19+
graphiql: true,
20+
schema: GraphQLSchema,
21+
}
22+
})
23+
);
24+
25+
module.exports = app;

src/graphql/graphql_lambda_handler.js

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
'use strict'
2+
3+
const awsServerlessExpress = require('aws-serverless-express');
4+
const app = require('./graphql_app');
5+
const server = awsServerlessExpress.createServer(app);
6+
7+
exports.handler = (event, context) => awsServerlessExpress.proxy(server, event, context);

0 commit comments

Comments
 (0)