├── .gitignore ├── README.md ├── functions ├── http-demo.ts └── s3-demo.ts ├── layer ├── README.md └── nodejs │ ├── package-lock.json │ └── package.json ├── package.json ├── serverless.yml ├── tsconfig.json ├── webpack.config.js ├── yarn-error.log └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .serverless 3 | pdflib-layer.zip -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # serverless-lambda-pdflib 2 | Simple Demo on using [pdf-lib](https://github.com/Hopding/pdf-lib) on AWS lambda. 3 | 4 | ### Install the dependencies 5 | 6 | ``` 7 | yarn install 8 | ``` 9 | 10 | ### Start the offline mode 11 | ``` 12 | yarn serverless 13 | ``` 14 | 15 | ### Modify the `profile` in Serverless.yml to use your AWS profile, then you can deploy to AWS 16 | ``` 17 | yarn sls deploy 18 | ``` 19 | -------------------------------------------------------------------------------- /functions/http-demo.ts: -------------------------------------------------------------------------------- 1 | import { PDFDocument, StandardFonts, rgb } from "pdf-lib"; 2 | import middy from "@middy/core"; 3 | import doNotWaitForEmptyEventLoop from "@middy/do-not-wait-for-empty-event-loop"; 4 | 5 | const generatePdf = async (): Promise => { 6 | const pdfDoc = await PDFDocument.create(); 7 | const timesRomanFont = await pdfDoc.embedFont(StandardFonts.TimesRoman); 8 | 9 | // Add a blank page to the document 10 | const page = pdfDoc.addPage(); 11 | 12 | // Get the width and height of the page 13 | const { width, height } = page.getSize(); 14 | 15 | // Draw a string of text toward the top of the page 16 | const fontSize = 30; 17 | page.drawText("Creating PDFs in Serverless is awesome!", { 18 | x: 50, 19 | y: height - 4 * fontSize, 20 | size: fontSize, 21 | font: timesRomanFont, 22 | color: rgb(0, 0.53, 0.71) 23 | }); 24 | 25 | // Serialize the PDFDocument to bytes (a Uint8Array) 26 | const pdfBytes = await pdfDoc.save(); 27 | return pdfBytes; 28 | }; 29 | const handler = async (event: any) => { 30 | const pdfBytes = await generatePdf(); 31 | const buffer = Buffer.from(pdfBytes); 32 | 33 | // console.log(event); 34 | // const stream = String.fromCharCode(...new Uint8Array(arrayBuffer)); 35 | return { 36 | statusCode: 200, 37 | headers: { 38 | "Content-type": "application/pdf" 39 | }, 40 | body: buffer.toString("base64"), 41 | isBase64Encoded: true 42 | }; 43 | }; 44 | 45 | export const generate = middy(handler).use(doNotWaitForEmptyEventLoop()); 46 | -------------------------------------------------------------------------------- /functions/s3-demo.ts: -------------------------------------------------------------------------------- 1 | import { PDFDocument, rgb } from "pdf-lib"; 2 | import aws from "aws-sdk"; 3 | import middy from "@middy/core"; 4 | import doNotWaitForEmptyEventLoop from "@middy/do-not-wait-for-empty-event-loop"; 5 | const s3 = new aws.S3({ region: "ap-southeast-2" }); 6 | 7 | const downloadFromS3ToStream = ( 8 | bucketName: string, 9 | keyName: string 10 | ): Promise => { 11 | return new Promise((resolve, reject) => { 12 | s3.getObject( 13 | { Bucket: bucketName, Key: keyName }, 14 | (error, data: aws.S3.GetObjectOutput) => { 15 | if (error) { 16 | console.error(error); 17 | reject(error); 18 | } else { 19 | resolve(data.Body); 20 | } 21 | } 22 | ); 23 | }); 24 | }; 25 | 26 | const saveToS3 = ( 27 | bucketName: string, 28 | keyName: string, 29 | Body: Buffer 30 | ): Promise => { 31 | return new Promise((resolve, reject) => { 32 | s3.putObject({ Bucket: bucketName, Key: keyName, Body }, (error, data) => { 33 | if (error) { 34 | console.error(error); 35 | reject(error); 36 | } else { 37 | resolve(data); 38 | } 39 | }); 40 | }); 41 | }; 42 | 43 | const handler = async (event: any) => { 44 | const bucketName = 45 | event.Records[0] && 46 | event.Records[0].s3 && 47 | event.Records[0].s3.bucket && 48 | event.Records[0].s3.bucket.name; 49 | const objectKey = 50 | event.Records[0] && 51 | event.Records[0].s3 && 52 | event.Records[0].s3.object && 53 | event.Records[0].s3.object.key; 54 | if (bucketName && objectKey) { 55 | const fileStream = await downloadFromS3ToStream(bucketName, objectKey); 56 | const pdfDoc = await PDFDocument.load(fileStream, { 57 | ignoreEncryption: true 58 | }); 59 | const newPage = pdfDoc.insertPage(0); 60 | 61 | const { height } = newPage.getSize(); 62 | const fontSize = 30; 63 | newPage.drawText("Top Secret!", { 64 | x: 50, 65 | y: height - 4 * fontSize, 66 | size: fontSize, 67 | color: rgb(0, 0.53, 0.71) 68 | }); 69 | const data = await pdfDoc.saveAsBase64(); 70 | 71 | await saveToS3( 72 | bucketName, 73 | `processed_${objectKey}`, 74 | new Buffer(data, "base64") 75 | ); 76 | return {}; 77 | } 78 | }; 79 | 80 | export const generate = middy(handler).use(doNotWaitForEmptyEventLoop()); 81 | -------------------------------------------------------------------------------- /layer/README.md: -------------------------------------------------------------------------------- 1 | # Generate the layer for running pdf-lib 2 | ```shell 3 | cd nodejs 4 | npm install 5 | cd .. 6 | zip -9r pdflib-layer.zip nodejs 7 | ``` -------------------------------------------------------------------------------- /layer/nodejs/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pdflib-layer", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@pdf-lib/standard-fonts": { 8 | "version": "0.0.4", 9 | "resolved": "https://registry.npmjs.org/@pdf-lib/standard-fonts/-/standard-fonts-0.0.4.tgz", 10 | "integrity": "sha512-2pg8hXnChVAF6aSFraXtwB0cx/AgE15FvuLJbdPJSq9LYp1xMp0lapH4+t1HsdD9cA05rnWYLqlEBwS4YK1jLg==", 11 | "requires": { 12 | "base64-arraybuffer": "^0.1.5", 13 | "pako": "^1.0.6" 14 | } 15 | }, 16 | "base64-arraybuffer": { 17 | "version": "0.1.5", 18 | "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", 19 | "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=" 20 | }, 21 | "pako": { 22 | "version": "1.0.11", 23 | "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", 24 | "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" 25 | }, 26 | "pdf-lib": { 27 | "version": "1.3.1", 28 | "resolved": "https://registry.npmjs.org/pdf-lib/-/pdf-lib-1.3.1.tgz", 29 | "integrity": "sha512-pP2qW/GH328C4idPbYbY4o0deStG62w7IpUlGv8liYBB+XOa2ntVdPZa8Q5p0eP1av3gtP6NYOSGk0NKC/xN8w==", 30 | "requires": { 31 | "@pdf-lib/standard-fonts": "^0.0.4", 32 | "pako": "^1.0.10", 33 | "png-ts": "^0.0.3", 34 | "tslib": "^1.10.0" 35 | } 36 | }, 37 | "png-ts": { 38 | "version": "0.0.3", 39 | "resolved": "https://registry.npmjs.org/png-ts/-/png-ts-0.0.3.tgz", 40 | "integrity": "sha512-Qwn3yMfbrbaN86QjrDAqD1UVJc4AV4hvBCx5Dv9libLd6D20xKtgOFs/UcvD0nnjxWlgS12kEVWCDFd9ZtwB+g==", 41 | "requires": { 42 | "pako": "^1.0.6" 43 | } 44 | }, 45 | "tslib": { 46 | "version": "1.10.0", 47 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", 48 | "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /layer/nodejs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pdflib-layer", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "pdf-lib": "^1.3.1" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serverless-lambda-pdflib", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "repository": "git@github.com:crespowang/serverless-lambda-pdflib.git", 6 | "author": "Crespo Wang ", 7 | "license": "MIT", 8 | "scripts": { 9 | "serverless": "sls offline start --port 3003 --stage dev --basePath / --prefix dev --location .webpack/service" 10 | }, 11 | "dependencies": { 12 | "@middy/core": "^1.0.0-beta.6", 13 | "@middy/do-not-wait-for-empty-event-loop": "^1.0.0-beta.6", 14 | "@types/aws-lambda": "^8.10.42", 15 | "aws-sdk": "^2.624.0", 16 | "pdf-lib": "^1.3.1", 17 | "serverless": "1.64.0", 18 | "typescript": "^3.7.5" 19 | }, 20 | "devDependencies": { 21 | "@types/node": "^13.7.2", 22 | "serverless-domain-manager": "^3.3.1", 23 | "serverless-offline": "^6.0.0-alpha.67", 24 | "serverless-webpack": "^5.3.1", 25 | "ts-loader": "^6.2.1", 26 | "webpack": "^4.41.6", 27 | "webpack-node-externals": "^1.7.2" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /serverless.yml: -------------------------------------------------------------------------------- 1 | service: serverless-lambda-pdflib 2 | frameworkVersion: "1.64.0" 3 | 4 | provider: 5 | name: aws 6 | versionFunctions: false 7 | runtime: nodejs12.x 8 | region: ap-southeast-2 9 | stage: dev 10 | profile: hotpuma 11 | apiGateway: 12 | binaryMediaTypes: 13 | - "application/pdf" 14 | iamRoleStatements: 15 | - Effect: Allow 16 | Action: 17 | - "s3:GetObject" 18 | - "s3:PutObject" 19 | Resource: 20 | - arn:aws:s3:::pdflib-bucket/* 21 | s3: 22 | demoBucket: 23 | name: pdflib-bucket 24 | bucketEncryption: 25 | ServerSideEncryptionConfiguration: 26 | - ServerSideEncryptionByDefault: 27 | SSEAlgorithm: AES256 28 | versioningConfiguration: 29 | Status: Suspended 30 | plugins: 31 | - serverless-webpack 32 | - serverless-domain-manager 33 | - serverless-offline 34 | 35 | layers: 36 | Pdflib: 37 | name: Pdflib 38 | compatibleRuntimes: 39 | - nodejs12.x 40 | description: Required for Pdflib 41 | package: 42 | artifact: layer/pdflib-layer.zip 43 | custom: 44 | webpack: 45 | webpackConfig: webpack.config.js 46 | includeModules: 47 | forceExclude: 48 | - pdf-lib 49 | - aws-sdk 50 | packager: "yarn" 51 | 52 | serverless-offline: 53 | location: .webpack/service 54 | 55 | customDomain: 56 | domainName: labs.mianio.com 57 | basePath: pdflib 58 | stage: ${self:provider.stage} 59 | createRoute53Record: true 60 | 61 | functions: 62 | pdflib-demo-s3: 63 | handler: functions/s3-demo.generate 64 | layers: 65 | - { Ref: PdflibLambdaLayer } 66 | events: 67 | - s3: 68 | bucket: demoBucket 69 | event: s3:ObjectCreated:* 70 | rules: 71 | - prefix: uploads/ 72 | - suffix: .pdf 73 | 74 | pdflib-demo-http: 75 | handler: functions/http-demo.generate 76 | layers: 77 | - { Ref: PdflibLambdaLayer } 78 | events: 79 | - http: 80 | path: demo 81 | method: get 82 | cors: true 83 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "esnext", 4 | // Target latest version of ECMAScript. 5 | 6 | "target": "es2019", 7 | // Search under node_modules for non-relative imports. 8 | "moduleResolution": "node", 9 | // Process & infer types from .js files. 10 | "allowJs": false, 11 | 12 | // Enable strictest settings like strictNullChecks & noImplicitAny. 13 | "strict": true, 14 | // Disallow features that require cross-file information for emit. 15 | "isolatedModules": false, 16 | // Import non-ES modules as default imports. 17 | "esModuleInterop": true, 18 | // Enable strict null checks 19 | "strictNullChecks": false, 20 | "skipLibCheck": true 21 | }, 22 | "include": ["**/*.ts"], 23 | "exclude": [".serverless", ".webpack", "node_modules"] 24 | } 25 | -------------------------------------------------------------------------------- /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 | module.exports = { 6 | entry: slsw.lib.entries, 7 | target: "node", 8 | mode: slsw.lib.webpack.isLocal ? "development" : "production", 9 | node: { 10 | __dirname: true 11 | }, 12 | optimization: { 13 | minimize: false 14 | }, 15 | externals: [nodeExternals()], 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.ts$/, 20 | loader: "ts-loader" 21 | } 22 | ] 23 | }, 24 | resolve: { 25 | extensions: [".ts", ".js"] 26 | }, 27 | output: { 28 | libraryTarget: "commonjs2", 29 | path: path.join(__dirname, ".webpack"), 30 | filename: "[name].js" 31 | }, 32 | plugins: [ 33 | new webpack.EnvironmentPlugin({ 34 | NODE_ENV: "development" 35 | }) 36 | ] 37 | }; 38 | --------------------------------------------------------------------------------