├── .gitignore ├── .serverless ├── cloudformation-template-create-stack.json ├── cloudformation-template-update-stack.json ├── serverless-pdf-demo.zip └── serverless-state.json ├── README.md ├── babel.config.js ├── bin └── wkhtmltopdf ├── functions └── pdf.ts ├── package.json ├── serverless.yml ├── webpack.config.js ├── wkhtmltopdf ├── yarn-error.log └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | -------------------------------------------------------------------------------- /.serverless/cloudformation-template-create-stack.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "The AWS CloudFormation template for this Serverless application", 4 | "Resources": { 5 | "ServerlessDeploymentBucket": { 6 | "Type": "AWS::S3::Bucket", 7 | "Properties": { 8 | "AccelerateConfiguration": { 9 | "AccelerationStatus": "Enabled" 10 | } 11 | } 12 | } 13 | }, 14 | "Outputs": { 15 | "ServerlessDeploymentBucketName": { 16 | "Value": { 17 | "Ref": "ServerlessDeploymentBucket" 18 | } 19 | }, 20 | "ServerlessDeploymentBucketAccelerated": { 21 | "Value": true 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /.serverless/cloudformation-template-update-stack.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "The AWS CloudFormation template for this Serverless application", 4 | "Resources": { 5 | "ServerlessDeploymentBucket": { 6 | "Type": "AWS::S3::Bucket", 7 | "Properties": { 8 | "AccelerateConfiguration": { 9 | "AccelerationStatus": "Enabled" 10 | } 11 | } 12 | }, 13 | "GiveDashmeDashtheDashpdfLogGroup": { 14 | "Type": "AWS::Logs::LogGroup", 15 | "Properties": { 16 | "LogGroupName": "/aws/lambda/serverless-pdf-demo-dev-give-me-the-pdf" 17 | } 18 | }, 19 | "IamRoleLambdaExecution": { 20 | "Type": "AWS::IAM::Role", 21 | "Properties": { 22 | "AssumeRolePolicyDocument": { 23 | "Version": "2012-10-17", 24 | "Statement": [ 25 | { 26 | "Effect": "Allow", 27 | "Principal": { 28 | "Service": [ 29 | "lambda.amazonaws.com" 30 | ] 31 | }, 32 | "Action": [ 33 | "sts:AssumeRole" 34 | ] 35 | } 36 | ] 37 | }, 38 | "Policies": [ 39 | { 40 | "PolicyName": { 41 | "Fn::Join": [ 42 | "-", 43 | [ 44 | "dev", 45 | "serverless-pdf-demo", 46 | "lambda" 47 | ] 48 | ] 49 | }, 50 | "PolicyDocument": { 51 | "Version": "2012-10-17", 52 | "Statement": [ 53 | { 54 | "Effect": "Allow", 55 | "Action": [ 56 | "logs:CreateLogStream" 57 | ], 58 | "Resource": [ 59 | { 60 | "Fn::Sub": "arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/serverless-pdf-demo-dev-give-me-the-pdf:*" 61 | } 62 | ] 63 | }, 64 | { 65 | "Effect": "Allow", 66 | "Action": [ 67 | "logs:PutLogEvents" 68 | ], 69 | "Resource": [ 70 | { 71 | "Fn::Sub": "arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/serverless-pdf-demo-dev-give-me-the-pdf:*:*" 72 | } 73 | ] 74 | } 75 | ] 76 | } 77 | } 78 | ], 79 | "Path": "/", 80 | "RoleName": { 81 | "Fn::Join": [ 82 | "-", 83 | [ 84 | "serverless-pdf-demo", 85 | "dev", 86 | "ap-southeast-2", 87 | "lambdaRole" 88 | ] 89 | ] 90 | } 91 | } 92 | }, 93 | "GiveDashmeDashtheDashpdfLambdaFunction": { 94 | "Type": "AWS::Lambda::Function", 95 | "Properties": { 96 | "Code": { 97 | "S3Bucket": { 98 | "Ref": "ServerlessDeploymentBucket" 99 | }, 100 | "S3Key": "serverless/serverless-pdf-demo/dev/1552024649681-2019-03-08T05:57:29.681Z/serverless-pdf-demo.zip" 101 | }, 102 | "FunctionName": "serverless-pdf-demo-dev-give-me-the-pdf", 103 | "Handler": "functions/pdf.generate", 104 | "MemorySize": 1024, 105 | "Role": { 106 | "Fn::GetAtt": [ 107 | "IamRoleLambdaExecution", 108 | "Arn" 109 | ] 110 | }, 111 | "Runtime": "nodejs8.10", 112 | "Timeout": 20 113 | }, 114 | "DependsOn": [ 115 | "GiveDashmeDashtheDashpdfLogGroup", 116 | "IamRoleLambdaExecution" 117 | ] 118 | }, 119 | "ApiGatewayRestApi": { 120 | "Type": "AWS::ApiGateway::RestApi", 121 | "Properties": { 122 | "Name": "dev-serverless-pdf-demo", 123 | "EndpointConfiguration": { 124 | "Types": [ 125 | "EDGE" 126 | ] 127 | } 128 | } 129 | }, 130 | "ApiGatewayResourcePdf": { 131 | "Type": "AWS::ApiGateway::Resource", 132 | "Properties": { 133 | "ParentId": { 134 | "Fn::GetAtt": [ 135 | "ApiGatewayRestApi", 136 | "RootResourceId" 137 | ] 138 | }, 139 | "PathPart": "pdf", 140 | "RestApiId": { 141 | "Ref": "ApiGatewayRestApi" 142 | } 143 | } 144 | }, 145 | "ApiGatewayMethodPdfOptions": { 146 | "Type": "AWS::ApiGateway::Method", 147 | "Properties": { 148 | "AuthorizationType": "NONE", 149 | "HttpMethod": "OPTIONS", 150 | "MethodResponses": [ 151 | { 152 | "StatusCode": "200", 153 | "ResponseParameters": { 154 | "method.response.header.Access-Control-Allow-Origin": true, 155 | "method.response.header.Access-Control-Allow-Headers": true, 156 | "method.response.header.Access-Control-Allow-Methods": true, 157 | "method.response.header.Access-Control-Allow-Credentials": true 158 | }, 159 | "ResponseModels": {} 160 | } 161 | ], 162 | "RequestParameters": {}, 163 | "Integration": { 164 | "Type": "MOCK", 165 | "RequestTemplates": { 166 | "application/json": "{statusCode:200}" 167 | }, 168 | "ContentHandling": "CONVERT_TO_TEXT", 169 | "IntegrationResponses": [ 170 | { 171 | "StatusCode": "200", 172 | "ResponseParameters": { 173 | "method.response.header.Access-Control-Allow-Origin": "'*'", 174 | "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent'", 175 | "method.response.header.Access-Control-Allow-Methods": "'OPTIONS,GET'", 176 | "method.response.header.Access-Control-Allow-Credentials": "'false'" 177 | }, 178 | "ResponseTemplates": { 179 | "application/json": "#set($origin = $input.params(\"Origin\"))\n#if($origin == \"\") #set($origin = $input.params(\"origin\")) #end\n#if($origin == \"*\") #set($context.responseOverride.header.Access-Control-Allow-Origin = $origin) #end" 180 | } 181 | } 182 | ] 183 | }, 184 | "ResourceId": { 185 | "Ref": "ApiGatewayResourcePdf" 186 | }, 187 | "RestApiId": { 188 | "Ref": "ApiGatewayRestApi" 189 | } 190 | } 191 | }, 192 | "ApiGatewayMethodPdfGet": { 193 | "Type": "AWS::ApiGateway::Method", 194 | "Properties": { 195 | "HttpMethod": "GET", 196 | "RequestParameters": {}, 197 | "ResourceId": { 198 | "Ref": "ApiGatewayResourcePdf" 199 | }, 200 | "RestApiId": { 201 | "Ref": "ApiGatewayRestApi" 202 | }, 203 | "ApiKeyRequired": false, 204 | "AuthorizationType": "NONE", 205 | "Integration": { 206 | "IntegrationHttpMethod": "POST", 207 | "Type": "AWS_PROXY", 208 | "Uri": { 209 | "Fn::Join": [ 210 | "", 211 | [ 212 | "arn:", 213 | { 214 | "Ref": "AWS::Partition" 215 | }, 216 | ":apigateway:", 217 | { 218 | "Ref": "AWS::Region" 219 | }, 220 | ":lambda:path/2015-03-31/functions/", 221 | { 222 | "Fn::GetAtt": [ 223 | "GiveDashmeDashtheDashpdfLambdaFunction", 224 | "Arn" 225 | ] 226 | }, 227 | "/invocations" 228 | ] 229 | ] 230 | } 231 | }, 232 | "MethodResponses": [] 233 | } 234 | }, 235 | "ApiGatewayDeployment1552024649800": { 236 | "Type": "AWS::ApiGateway::Deployment", 237 | "Properties": { 238 | "RestApiId": { 239 | "Ref": "ApiGatewayRestApi" 240 | }, 241 | "StageName": "dev" 242 | }, 243 | "DependsOn": [ 244 | "ApiGatewayMethodPdfOptions", 245 | "ApiGatewayMethodPdfGet" 246 | ] 247 | }, 248 | "GiveDashmeDashtheDashpdfLambdaPermissionApiGateway": { 249 | "Type": "AWS::Lambda::Permission", 250 | "Properties": { 251 | "FunctionName": { 252 | "Fn::GetAtt": [ 253 | "GiveDashmeDashtheDashpdfLambdaFunction", 254 | "Arn" 255 | ] 256 | }, 257 | "Action": "lambda:InvokeFunction", 258 | "Principal": { 259 | "Fn::Join": [ 260 | "", 261 | [ 262 | "apigateway.", 263 | { 264 | "Ref": "AWS::URLSuffix" 265 | } 266 | ] 267 | ] 268 | }, 269 | "SourceArn": { 270 | "Fn::Join": [ 271 | "", 272 | [ 273 | "arn:", 274 | { 275 | "Ref": "AWS::Partition" 276 | }, 277 | ":execute-api:", 278 | { 279 | "Ref": "AWS::Region" 280 | }, 281 | ":", 282 | { 283 | "Ref": "AWS::AccountId" 284 | }, 285 | ":", 286 | { 287 | "Ref": "ApiGatewayRestApi" 288 | }, 289 | "/*/*" 290 | ] 291 | ] 292 | } 293 | } 294 | } 295 | }, 296 | "Outputs": { 297 | "ServerlessDeploymentBucketName": { 298 | "Value": { 299 | "Ref": "ServerlessDeploymentBucket" 300 | } 301 | }, 302 | "ServerlessDeploymentBucketAccelerated": { 303 | "Value": true 304 | }, 305 | "ServiceEndpoint": { 306 | "Description": "URL of the service endpoint", 307 | "Value": { 308 | "Fn::Join": [ 309 | "", 310 | [ 311 | "https://", 312 | { 313 | "Ref": "ApiGatewayRestApi" 314 | }, 315 | ".execute-api.ap-southeast-2.", 316 | { 317 | "Ref": "AWS::URLSuffix" 318 | }, 319 | "/dev" 320 | ] 321 | ] 322 | } 323 | } 324 | } 325 | } -------------------------------------------------------------------------------- /.serverless/serverless-pdf-demo.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crespowang/serverless-lambda-pdf-generation/1791c94c64e4439c3a068234b4e06c3d1f0ae7b7/.serverless/serverless-pdf-demo.zip -------------------------------------------------------------------------------- /.serverless/serverless-state.json: -------------------------------------------------------------------------------- 1 | { 2 | "service": { 3 | "service": "serverless-pdf-demo", 4 | "serviceObject": { 5 | "name": "serverless-pdf-demo" 6 | }, 7 | "provider": { 8 | "stage": "dev", 9 | "region": "ap-southeast-2", 10 | "variableSyntax": "\\${([ ~:a-zA-Z0-9._@'\",\\-\\/\\(\\)]+?)}", 11 | "name": "aws", 12 | "versionFunctions": false, 13 | "runtime": "nodejs8.10", 14 | "profile": "hotpuma", 15 | "package": { 16 | "individually": true 17 | }, 18 | "remoteFunctionData": null, 19 | "compiledCloudFormationTemplate": { 20 | "AWSTemplateFormatVersion": "2010-09-09", 21 | "Description": "The AWS CloudFormation template for this Serverless application", 22 | "Resources": { 23 | "ServerlessDeploymentBucket": { 24 | "Type": "AWS::S3::Bucket", 25 | "Properties": { 26 | "AccelerateConfiguration": { 27 | "AccelerationStatus": "Enabled" 28 | } 29 | } 30 | }, 31 | "GiveDashmeDashtheDashpdfLogGroup": { 32 | "Type": "AWS::Logs::LogGroup", 33 | "Properties": { 34 | "LogGroupName": "/aws/lambda/serverless-pdf-demo-dev-give-me-the-pdf" 35 | } 36 | }, 37 | "IamRoleLambdaExecution": { 38 | "Type": "AWS::IAM::Role", 39 | "Properties": { 40 | "AssumeRolePolicyDocument": { 41 | "Version": "2012-10-17", 42 | "Statement": [ 43 | { 44 | "Effect": "Allow", 45 | "Principal": { 46 | "Service": [ 47 | "lambda.amazonaws.com" 48 | ] 49 | }, 50 | "Action": [ 51 | "sts:AssumeRole" 52 | ] 53 | } 54 | ] 55 | }, 56 | "Policies": [ 57 | { 58 | "PolicyName": { 59 | "Fn::Join": [ 60 | "-", 61 | [ 62 | "dev", 63 | "serverless-pdf-demo", 64 | "lambda" 65 | ] 66 | ] 67 | }, 68 | "PolicyDocument": { 69 | "Version": "2012-10-17", 70 | "Statement": [ 71 | { 72 | "Effect": "Allow", 73 | "Action": [ 74 | "logs:CreateLogStream" 75 | ], 76 | "Resource": [ 77 | { 78 | "Fn::Sub": "arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/serverless-pdf-demo-dev-give-me-the-pdf:*" 79 | } 80 | ] 81 | }, 82 | { 83 | "Effect": "Allow", 84 | "Action": [ 85 | "logs:PutLogEvents" 86 | ], 87 | "Resource": [ 88 | { 89 | "Fn::Sub": "arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/serverless-pdf-demo-dev-give-me-the-pdf:*:*" 90 | } 91 | ] 92 | } 93 | ] 94 | } 95 | } 96 | ], 97 | "Path": "/", 98 | "RoleName": { 99 | "Fn::Join": [ 100 | "-", 101 | [ 102 | "serverless-pdf-demo", 103 | "dev", 104 | "ap-southeast-2", 105 | "lambdaRole" 106 | ] 107 | ] 108 | } 109 | } 110 | }, 111 | "GiveDashmeDashtheDashpdfLambdaFunction": { 112 | "Type": "AWS::Lambda::Function", 113 | "Properties": { 114 | "Code": { 115 | "S3Bucket": { 116 | "Ref": "ServerlessDeploymentBucket" 117 | }, 118 | "S3Key": "serverless/serverless-pdf-demo/dev/1552024649681-2019-03-08T05:57:29.681Z/serverless-pdf-demo.zip" 119 | }, 120 | "FunctionName": "serverless-pdf-demo-dev-give-me-the-pdf", 121 | "Handler": "functions/pdf.generate", 122 | "MemorySize": 1024, 123 | "Role": { 124 | "Fn::GetAtt": [ 125 | "IamRoleLambdaExecution", 126 | "Arn" 127 | ] 128 | }, 129 | "Runtime": "nodejs8.10", 130 | "Timeout": 20 131 | }, 132 | "DependsOn": [ 133 | "GiveDashmeDashtheDashpdfLogGroup", 134 | "IamRoleLambdaExecution" 135 | ] 136 | }, 137 | "ApiGatewayRestApi": { 138 | "Type": "AWS::ApiGateway::RestApi", 139 | "Properties": { 140 | "Name": "dev-serverless-pdf-demo", 141 | "EndpointConfiguration": { 142 | "Types": [ 143 | "EDGE" 144 | ] 145 | } 146 | } 147 | }, 148 | "ApiGatewayResourcePdf": { 149 | "Type": "AWS::ApiGateway::Resource", 150 | "Properties": { 151 | "ParentId": { 152 | "Fn::GetAtt": [ 153 | "ApiGatewayRestApi", 154 | "RootResourceId" 155 | ] 156 | }, 157 | "PathPart": "pdf", 158 | "RestApiId": { 159 | "Ref": "ApiGatewayRestApi" 160 | } 161 | } 162 | }, 163 | "ApiGatewayMethodPdfOptions": { 164 | "Type": "AWS::ApiGateway::Method", 165 | "Properties": { 166 | "AuthorizationType": "NONE", 167 | "HttpMethod": "OPTIONS", 168 | "MethodResponses": [ 169 | { 170 | "StatusCode": "200", 171 | "ResponseParameters": { 172 | "method.response.header.Access-Control-Allow-Origin": true, 173 | "method.response.header.Access-Control-Allow-Headers": true, 174 | "method.response.header.Access-Control-Allow-Methods": true, 175 | "method.response.header.Access-Control-Allow-Credentials": true 176 | }, 177 | "ResponseModels": {} 178 | } 179 | ], 180 | "RequestParameters": {}, 181 | "Integration": { 182 | "Type": "MOCK", 183 | "RequestTemplates": { 184 | "application/json": "{statusCode:200}" 185 | }, 186 | "ContentHandling": "CONVERT_TO_TEXT", 187 | "IntegrationResponses": [ 188 | { 189 | "StatusCode": "200", 190 | "ResponseParameters": { 191 | "method.response.header.Access-Control-Allow-Origin": "'*'", 192 | "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent'", 193 | "method.response.header.Access-Control-Allow-Methods": "'OPTIONS,GET'", 194 | "method.response.header.Access-Control-Allow-Credentials": "'false'" 195 | }, 196 | "ResponseTemplates": { 197 | "application/json": "#set($origin = $input.params(\"Origin\"))\n#if($origin == \"\") #set($origin = $input.params(\"origin\")) #end\n#if($origin == \"*\") #set($context.responseOverride.header.Access-Control-Allow-Origin = $origin) #end" 198 | } 199 | } 200 | ] 201 | }, 202 | "ResourceId": { 203 | "Ref": "ApiGatewayResourcePdf" 204 | }, 205 | "RestApiId": { 206 | "Ref": "ApiGatewayRestApi" 207 | } 208 | } 209 | }, 210 | "ApiGatewayMethodPdfGet": { 211 | "Type": "AWS::ApiGateway::Method", 212 | "Properties": { 213 | "HttpMethod": "GET", 214 | "RequestParameters": {}, 215 | "ResourceId": { 216 | "Ref": "ApiGatewayResourcePdf" 217 | }, 218 | "RestApiId": { 219 | "Ref": "ApiGatewayRestApi" 220 | }, 221 | "ApiKeyRequired": false, 222 | "AuthorizationType": "NONE", 223 | "Integration": { 224 | "IntegrationHttpMethod": "POST", 225 | "Type": "AWS_PROXY", 226 | "Uri": { 227 | "Fn::Join": [ 228 | "", 229 | [ 230 | "arn:", 231 | { 232 | "Ref": "AWS::Partition" 233 | }, 234 | ":apigateway:", 235 | { 236 | "Ref": "AWS::Region" 237 | }, 238 | ":lambda:path/2015-03-31/functions/", 239 | { 240 | "Fn::GetAtt": [ 241 | "GiveDashmeDashtheDashpdfLambdaFunction", 242 | "Arn" 243 | ] 244 | }, 245 | "/invocations" 246 | ] 247 | ] 248 | } 249 | }, 250 | "MethodResponses": [] 251 | } 252 | }, 253 | "ApiGatewayDeployment1552024649800": { 254 | "Type": "AWS::ApiGateway::Deployment", 255 | "Properties": { 256 | "RestApiId": { 257 | "Ref": "ApiGatewayRestApi" 258 | }, 259 | "StageName": "dev" 260 | }, 261 | "DependsOn": [ 262 | "ApiGatewayMethodPdfOptions", 263 | "ApiGatewayMethodPdfGet" 264 | ] 265 | }, 266 | "GiveDashmeDashtheDashpdfLambdaPermissionApiGateway": { 267 | "Type": "AWS::Lambda::Permission", 268 | "Properties": { 269 | "FunctionName": { 270 | "Fn::GetAtt": [ 271 | "GiveDashmeDashtheDashpdfLambdaFunction", 272 | "Arn" 273 | ] 274 | }, 275 | "Action": "lambda:InvokeFunction", 276 | "Principal": { 277 | "Fn::Join": [ 278 | "", 279 | [ 280 | "apigateway.", 281 | { 282 | "Ref": "AWS::URLSuffix" 283 | } 284 | ] 285 | ] 286 | }, 287 | "SourceArn": { 288 | "Fn::Join": [ 289 | "", 290 | [ 291 | "arn:", 292 | { 293 | "Ref": "AWS::Partition" 294 | }, 295 | ":execute-api:", 296 | { 297 | "Ref": "AWS::Region" 298 | }, 299 | ":", 300 | { 301 | "Ref": "AWS::AccountId" 302 | }, 303 | ":", 304 | { 305 | "Ref": "ApiGatewayRestApi" 306 | }, 307 | "/*/*" 308 | ] 309 | ] 310 | } 311 | } 312 | } 313 | }, 314 | "Outputs": { 315 | "ServerlessDeploymentBucketName": { 316 | "Value": { 317 | "Ref": "ServerlessDeploymentBucket" 318 | } 319 | }, 320 | "ServerlessDeploymentBucketAccelerated": { 321 | "Value": true 322 | }, 323 | "ServiceEndpoint": { 324 | "Description": "URL of the service endpoint", 325 | "Value": { 326 | "Fn::Join": [ 327 | "", 328 | [ 329 | "https://", 330 | { 331 | "Ref": "ApiGatewayRestApi" 332 | }, 333 | ".execute-api.ap-southeast-2.", 334 | { 335 | "Ref": "AWS::URLSuffix" 336 | }, 337 | "/dev" 338 | ] 339 | ] 340 | } 341 | } 342 | } 343 | }, 344 | "coreCloudFormationTemplate": { 345 | "AWSTemplateFormatVersion": "2010-09-09", 346 | "Description": "The AWS CloudFormation template for this Serverless application", 347 | "Resources": { 348 | "ServerlessDeploymentBucket": { 349 | "Type": "AWS::S3::Bucket", 350 | "Properties": { 351 | "AccelerateConfiguration": { 352 | "AccelerationStatus": "Enabled" 353 | } 354 | } 355 | } 356 | }, 357 | "Outputs": { 358 | "ServerlessDeploymentBucketName": { 359 | "Value": { 360 | "Ref": "ServerlessDeploymentBucket" 361 | } 362 | }, 363 | "ServerlessDeploymentBucketAccelerated": { 364 | "Value": true 365 | } 366 | } 367 | }, 368 | "vpc": {} 369 | }, 370 | "custom": { 371 | "apigwBinary": { 372 | "types": [ 373 | "application/pdf" 374 | ] 375 | }, 376 | "webpack": { 377 | "webpackConfig": "webpack.config.js", 378 | "includeModules": true, 379 | "packager": "yarn" 380 | }, 381 | "serverless-offline": { 382 | "location": ".webpack/service" 383 | }, 384 | "customDomain": { 385 | "domainName": "labs.mianio.com", 386 | "basePath": "api", 387 | "stage": "dev", 388 | "createRoute53Record": true 389 | } 390 | }, 391 | "plugins": [ 392 | "serverless-webpack", 393 | "serverless-offline", 394 | "serverless-domain-manager", 395 | "serverless-apigw-binary" 396 | ], 397 | "pluginsData": {}, 398 | "functions": { 399 | "give-me-the-pdf": { 400 | "handler": "functions/pdf.generate", 401 | "timeout": 20, 402 | "events": [ 403 | { 404 | "http": { 405 | "path": "pdf", 406 | "method": "get", 407 | "cors": { 408 | "origins": [ 409 | "*" 410 | ], 411 | "origin": "*", 412 | "methods": [ 413 | "OPTIONS", 414 | "GET" 415 | ], 416 | "headers": [ 417 | "Content-Type", 418 | "X-Amz-Date", 419 | "Authorization", 420 | "X-Api-Key", 421 | "X-Amz-Security-Token", 422 | "X-Amz-User-Agent" 423 | ], 424 | "allowCredentials": false 425 | }, 426 | "parameters": { 427 | "headers": { 428 | "Accept": "application/pdf" 429 | } 430 | }, 431 | "integration": "AWS_PROXY" 432 | } 433 | } 434 | ], 435 | "name": "serverless-pdf-demo-dev-give-me-the-pdf", 436 | "package": { 437 | "artifact": ".serverless/serverless-pdf-demo.zip" 438 | }, 439 | "memory": 1024, 440 | "runtime": "nodejs8.10", 441 | "vpc": {} 442 | } 443 | }, 444 | "layers": {} 445 | }, 446 | "package": { 447 | "artifactDirectoryName": "serverless/serverless-pdf-demo/dev/1552024649681-2019-03-08T05:57:29.681Z", 448 | "artifact": "" 449 | } 450 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## serverless-lambda-pdf-generation 2 | 3 | First run `yarn install` 4 | 5 | To see it offline run 6 | 7 | `yarn serverless` 8 | 9 | To deploy it, you will need to change *profile* to use your own AWS profile, and change domainName to your domain name, then do `sls deploy`. 10 | After deploy, in my configuration, you can see it at https://labs.mianio.com/api/pdf 11 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | targets: { 7 | node: true 8 | } 9 | } 10 | ], 11 | "@babel/typescript" 12 | ] 13 | }; 14 | -------------------------------------------------------------------------------- /bin/wkhtmltopdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crespowang/serverless-lambda-pdf-generation/1791c94c64e4439c3a068234b4e06c3d1f0ae7b7/bin/wkhtmltopdf -------------------------------------------------------------------------------- /functions/pdf.ts: -------------------------------------------------------------------------------- 1 | import middy from "middy"; 2 | import wkhtmltopdf from "wkhtmltopdf"; 3 | import { 4 | cors, 5 | doNotWaitForEmptyEventLoop, 6 | httpErrorHandler 7 | } from "middy/middlewares"; 8 | 9 | process.env["PATH"] = 10 | process.env["PATH"] + ":" + process.env["LAMBDA_TASK_ROOT"]; 11 | 12 | const getPDFStream = async (): Promise => { 13 | const stream: any = wkhtmltopdf("hello world"); 14 | const chunks = []; 15 | stream.on("data", chunk => { 16 | chunks.push(chunk); 17 | }); 18 | return new Promise((resolve, reject) => { 19 | stream.on("end", () => resolve(Buffer.concat(chunks))); 20 | stream.on("error", reject); 21 | }); 22 | }; 23 | const handler = async event => { 24 | try { 25 | const pdfStream = await getPDFStream(); 26 | return { 27 | statusCode: 200, 28 | isBase64Encoded: true, 29 | headers: { 30 | "Content-type": "application/pdf", 31 | "accept-ranges": "bytes" 32 | }, 33 | body: pdfStream.toString("base64") 34 | }; 35 | } catch (error) { 36 | console.log(error); 37 | } 38 | }; 39 | 40 | export const generate = middy(handler) 41 | .use(cors()) 42 | .use(doNotWaitForEmptyEventLoop()) 43 | .use(httpErrorHandler()); 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serverless-lamda-pdf-generation", 3 | "version": "1.0.0", 4 | "description": "Sample code to generate PDF with Serverless on Lambda", 5 | "main": "index.js", 6 | "repository": "git@github.com:crespowang/serverless-lambda-pdf-generation.git", 7 | "author": "Crespo Wang ", 8 | "license": "MIT", 9 | "scripts": { 10 | "serverless": "sls offline start --port 3003 --stage dev --basePath / --prefix dev --location .webpack/service" 11 | }, 12 | "dependencies": { 13 | "@babel/cli": "^7.2.3", 14 | "@babel/core": "^7.3.4", 15 | "@babel/preset-env": "^7.3.4", 16 | "@babel/preset-typescript": "^7.3.3", 17 | "@babel/runtime": "^7.3.4", 18 | "@types/node": "^11.10.5", 19 | "babel-loader": "^8.0.5", 20 | "inq-webpack-plugin-copy": "^1.0.1", 21 | "middy": "^0.22.1", 22 | "serverless": "1.37.0", 23 | "serverless-apigw-binary": "^0.4.4", 24 | "serverless-domain-manager": "^3.1.0", 25 | "serverless-offline": "^4.8.1", 26 | "serverless-webpack": "^5.2.0", 27 | "typescript": "^3.3.3333", 28 | "webpack": "^4.29.6", 29 | "webpack-node-externals": "^1.7.2", 30 | "wkhtmltopdf": "^0.3.4" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /serverless.yml: -------------------------------------------------------------------------------- 1 | service: serverless-pdf-demo 2 | frameworkVersion: "1.37.0" 3 | 4 | provider: 5 | name: aws 6 | versionFunctions: false 7 | runtime: nodejs8.10 8 | region: ap-southeast-2 9 | stage: dev 10 | profile: hotpuma 11 | apiGateway: 12 | binaryMediaTypes: # Optional binary media types the API might return 13 | - 'application/pdf' 14 | 15 | package: 16 | individually: true 17 | 18 | plugins: 19 | - serverless-webpack 20 | - serverless-offline 21 | - serverless-domain-manager 22 | - serverless-apigw-binary 23 | 24 | custom: 25 | 26 | webpack: 27 | webpackConfig: webpack.config.js 28 | includeModules: true 29 | packager: "yarn" 30 | 31 | serverless-offline: 32 | location: .webpack/service 33 | 34 | customDomain: 35 | domainName: labs.mianio.com 36 | basePath: api 37 | stage: ${self:provider.stage} 38 | createRoute53Record: true 39 | 40 | functions: 41 | give-me-the-pdf: 42 | handler: functions/pdf.generate 43 | timeout: 20 44 | events: 45 | - http: 46 | path: pdf 47 | method: get 48 | cors: true 49 | parameters: 50 | headers: 51 | Accept: "application/pdf" 52 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require("webpack"); 2 | const path = require("path"); 3 | const slsw = require("serverless-webpack"); 4 | const nodeExternals = require("webpack-node-externals"); 5 | const WebpackPluginCopy = require("inq-webpack-plugin-copy"); 6 | module.exports = { 7 | entry: slsw.lib.entries, 8 | target: "node", 9 | mode: slsw.lib.webpack.isLocal ? "development" : "production", 10 | node: { 11 | __dirname: true 12 | }, 13 | 14 | externals: [nodeExternals()], 15 | module: { 16 | rules: [ 17 | { 18 | test: /\.ts$/, 19 | loader: "babel-loader", 20 | options: { 21 | presets: [ 22 | [ 23 | "@babel/preset-env", 24 | { 25 | targets: { 26 | node: true 27 | } 28 | } 29 | ], 30 | "@babel/typescript" 31 | ] 32 | }, 33 | include: [__dirname], 34 | exclude: /node_modules/ 35 | } 36 | ] 37 | }, 38 | resolve: { 39 | extensions: [".ts", ".js"] 40 | }, 41 | output: { 42 | libraryTarget: "commonjs2", 43 | path: path.join(__dirname, ".webpack"), 44 | filename: "[name].js" 45 | }, 46 | plugins: [ 47 | new webpack.EnvironmentPlugin({ 48 | NODE_ENV: "development" 49 | }), 50 | new WebpackPluginCopy([ 51 | { 52 | from: "bin/wkhtmltopdf", 53 | to: "wkhtmltopdf", 54 | toType: "file", 55 | copyPermissions: true 56 | } 57 | ]) 58 | ] 59 | }; 60 | -------------------------------------------------------------------------------- /wkhtmltopdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crespowang/serverless-lambda-pdf-generation/1791c94c64e4439c3a068234b4e06c3d1f0ae7b7/wkhtmltopdf --------------------------------------------------------------------------------