# ๐Ÿฑ Building The Purrfect Cat API With Serverless ๐Ÿฑ In today's workshop, we will be creating [Catti](https://ganning127.github.io/catti/), a website that gives personalized images of cats to users! With AWS Lambda and Cloud9, we will build, create, and deploy this awesome serverless function! ## Contents 1. What is the Cloud? 2. What is AWS? 3. Catti: The backend 4. Catti: The frontend ![](https://i.imgur.com/aVGq6wJ.gif) ## โ˜๏ธ What is the Cloud? "The cloud" refers to servers that are accessed over the Internet, and the software and databases that run on those servers. By using cloud computing, companies don't have to manage physical servers themselves or run software applications on their own machines. ## ๐Ÿ’พ Before the Cloud Before there was cloud computing, there was just a lonely single computer. Companies and individuals needed to store all their data and software they had on their own hard drives and servers. If they ran out of storage, they would need to go out and buy more hard drives. ## โ“ The Problem? This way of treating data is not scalable. For example, if you were running a business, and your business suddenly saw a spike in purchases, your servers might very likely go down, resulting in a loss of both revenue and customer trust. ## ๐Ÿค— After cloud Many of us use cloud services every day. When we update our status on social media, binge a new streaming series, or check our bank accounts we're most likely using applications that are hosted by cloud services. These apps are accessed through an internet connection rather than installed on our hard drives or devices. ## ๐Ÿ’ป What the heck is AWS? Amazon Web Services is the worldโ€™s most comprehensive and broadly adopted cloud platform, offering over 200 fully-featured services from data centers globally. Millions of customersโ€”including the fastest-growing startups, largest enterprises, and leading government agenciesโ€”are using AWS to lower costs, become more agile, and innovate faster. Basically, AWS is the largest public cloud computing platform. ### ๐Ÿ’ฐ Why should WE use AWS? - **Cost-effective**: AWS Lambda (which is what we will be using) is regularly 20 cents/1 million requests. - **Flexible**: AWS supports JavaScript, Java, .NET, PHP, Python, Ruby, Go, and C++ - **Scalable**: AWS minimizes costs during spikes or lulls in traffic, because AWS is pay-as-you-use. ## โœ… Catti Features โœ… - **Random Cat Pictures**: Choose from 20+ categories of cat pictures. - **Add any text**: Create a meme, or anything else. - **Rotate & flip the image:** Make the cat dizzy! - **Create the purrfect API**: The Catti web app is the cutest website ever. ## ๐Ÿ“ถ Overview of Steps Needed 1. Get a picture of a cat (if the user passes in a category (such as cute), we get an image of a cat with that category) 2. Manipulate the image (rotate, add text, flip) if the user has specified to do so (otherwise, do nothing to image) 3. Return a base64 string (binary data that represents an image) to the user 4. Display the base64 string on the HTML To do this, we will need AWS Lambda, an API Gateway, AWS Cloud9, Node.js, and npm packages. ![](https://i.imgur.com/UHpHy4t.png) ## ๐Ÿชœ Steps #### Get to Cloud9 1. Go to the [AWS Console](https://aws.amazon.com/console/), and click `login` 2. In the search bar at the top, search for `Cloud9` ![](https://i.imgur.com/JnWRaTC.png) #### Create a new enviroment 1. Click the orange button that says "Create environment" 2. Name the environment `catti` 3. Leave all other options as defaults and click "Create environment" 4. Click on the cloud-shaped icon in the top left, and select preferences 5. Go down to AWS settings, and turn off `AWS Toolkit` (this should reload the page) ![](https://i.imgur.com/66TFZ5J.png) > ๐Ÿ˜ฎ This creates a new Cloud9 enviroment for us, where we can code straight from our browser! #### โšก Create the lambda function Let's create our first AWS Lambda function from Cloud9! 1. Click on the "A" icon on the right panel 2. Click on the "A" icon again on the top panel in the popover 3. Name the function `catti` (leave all other defaults) 4. Select `empty-nodejs` > This allows us to code Catti in NodeJS, and gives us a template. 5. Select `API Gateway` under "Function trigger" (this is how we will access the API endpoint URL) > The API gateway is the URL we will use to access the Catti Lambda function endpoint. 6. Resource Path: `/`, Security: `NONE` > Since we want to be able to access the API from all websites, we don't need any security. 7. Accept all other defaults and click "Finish" #### โœ๏ธ Edit the lambda template 1. Navigate to `index.js` in the folders on the left pane 2. Delete the extra parameters so that the only one left is `event` (we won't be using the other parameters) > `event` contains information about the request, such as which query string parameters were passed in 4. Add `async` in front of the function parameters > We need to add async in front of our function here because we need to make sure JS knows that there will be background processes running before we try to return something from this function. ![](https://i.imgur.com/c02HDDe.png) #### ๐Ÿค™ Sending our first API call 1. Click on the big green button at the top titled `Run` 2. Click run again on the right pane 3. We get back `Hello world!` > If you noticed, this is exactly what we returned from our Lambda function! 5. We made our first AWS Lambda function!! ![](https://i.imgur.com/SHfxqvL.png) #### ๐Ÿˆ Getting a cat image 1. To get a cat image back, we will be using the CataaS API 2. The endpoint is: `https://cataas.com/cat` 3. Let's try returning that URL in our response body > The Cat as a Service API allows us to get random images of cats. > ![](https://i.imgur.com/CwCL6qI.png) #### ๐Ÿšข Deploying our lambda function 1. Open up the folders on the right side, all the way up to the lambda function level (orange function icon) 2. Right-click on the function name, and click `Deploy` (this will take a minute) > Deploying our Lambda function allows us to actually use it as an API endpoint! 3. Go to the AWS console, and search for `lambda` 4. Click the big number of how many lambda functions you have, and click into it 5. Find the lambda function you just made, and click it 6. Click the API Gateway button, and find the API endpoint > This should be a URL, which you will use to access the AWS Lambda function you just made! 7. Copy and paste that endpoint into Postman and send a request > [Postman](https://www.postman.com/downloads/) is an app that we can use to simulate API calls, without actually needing to write code! This is great for testing APIs in development. 8. You just got a cool cat picture back!!! ![](https://i.imgur.com/eXKxL4a.png) #### ๐Ÿ”จ Navigating to function In order to manipulate the image, we need to use the npm package `jimp`. To install jimp, head to your terminal and type: ```bash cd catti cd catti ``` > ๐Ÿ’ก cd means change directory > ![](https://i.imgur.com/uici11t.png) This will take you to the folder where the code of the function is being held. We need to go to the function directory so that we can access the jimp package when we install it. #### ๐Ÿงฐ Installing jimp npm packages are basically code that other people have written for you to use. To install jimp, type in your terminal: ```bash npm init -y npm install jimp ``` To use jimp in your code, we need to require it on line 1: ```js const Jimp = require('jimp'); ``` > `require` allows us to actually use the npm package we just installed in our code! #### ๐ŸŒŽ Adding Hello World to an image Let's create another async function named `manipulateImage`, which takes in a parameter of `url` In this function we need to load the image using jimp: ```js const image = await Jimp.read(url); ``` We also need to add the text "Hello World" to the image: ```js const font = await Jimp.loadFont(Jimp.FONT_SANS_64_WHITE); await image.print(font, 50, 50, "hello world!"); ``` > 50, 50 are the x and y coordinates of the text, which you can change to your heart's desire! Finally, we need to return a base64 string back to the API request: ```js image.getBuffer(Jimp.MIME_PNG, (err, buffer) => { response = Buffer.from(buffer).toString('base64'); }); ``` > ๐Ÿ’ก don't forget to call the function in `exports.handler` ```js const base64Str = await manipulateImg("https://cataas.com/cat"); ``` So far, the `manipulateImage` function should look like this: ```js async function manipulateImg(catUrl) { let response; const image = await Jimp.read(catUrl); const font = await Jimp.loadFont(Jimp.FONT_SANS_64_WHITE); await image.print(font, 50, 50, "hello world!"); image.getBuffer(Jimp.MIME_PNG, (err, buffer) => { console.log(buffer) response = Buffer.from(buffer).toString('base64'); }); return response; } ``` This function: - Adds "hello world!" to a random cat image, and returns the `base64` representation of that image. #### ๐Ÿ”„ Rotating & flipping the image To rotate and flip an image using Jimp, we need this line of code: ```js await image.rotate(rotateDeg); await image.flip(true, false); ``` > ๐Ÿ’ก the first argument to flip is a horizontal flip, the second is a vertical flip Add the code right after we load the image and before we add the text (we don't want our text to get lopsided too!) ![](https://i.imgur.com/DKjWbZT.png) #### Test the function out in Postman to make sure it works! #### ๐Ÿ“ Taking in a `text` parameter 1. Create a function named `getRequestParams`, taking in a parameter of `event`, which will be the api request object. 2. This function will get the value from the `text` parameter 3. Returns an object with each parameter and its corresponding value To get the value from the "text" parameter, we will use: ```js const text = event["queryStringParameters"]['text']; ``` Let's return this as an object ```js return {text} ``` We will call this function in the `exports.handler` with: ```js const requestObj = getRequestParams(event); ``` So far, the entire `getRequestParams` function should look like ```js function getRequestParams(event) { const text = event["queryStringParameters"]['text']; return {text}; } ``` > We creae this function to make it easier to access each request parameter that the user wanted. #### ๐Ÿ“ฑ Creating a response based on user text 1. We need to now pass in the `requestObj` to the `manipulateImage` function. 2. Comment out the lines for rotating and flipping the image for now 3. Add an if conditional, so the final `manipulateImage` function looks like: ```js async function manipulateImg(catUrl, requestObj) { const image = await Jimp.read(catUrl); // await image.rotate(54); // await image.flip(true, false); if (requestObj.text) { const font = await Jimp.loadFont(Jimp.FONT_SANS_64_BLACK); await image.print(font, 50, 50, requestObj.text); } image.getBuffer(Jimp.MIME_PNG, (err, buffer) => { console.log(buffer) response = Buffer.from(buffer).toString('base64'); }); return response; } ``` > This function will take input from the user for what they entered in the `text` query string parameter, and add it onto the image! #### ๐Ÿ–Š๏ธ Adding the rest of the parameters 1. In the `getRequestParams` function, we need to add code to get the `queryStringParameters` of: - category (such as: cute, fat, etc) - text (such as: i am da best) - rotate (such as: 54) - flip (such as: h, v, b) - random (such as: true, false) 2. Make sure to return these in the object! The `getRequestParams` function so far: ```js function getRequestParams(event) { const catTag = event["queryStringParameters"]['category']; const text = event["queryStringParameters"]['text']; const rotate = event["queryStringParameters"]['rotate']; // convert to int const flip = event["queryStringParameters"]['flip']; const random = event["queryStringParameters"]['random']; return { catTag, text, rotate, flip, random }; } ``` > Now all the paramters that was in the `event` object is now accessible within the `requestObj` object! #### ๐Ÿ—ณ๏ธ Getting a cat from a category 1. Create another function named `getCat`, which takes in a parameter of `tag`. > โœ… if the tag is defined, then we return a url to get a cat with that category > โŒ else, return a url to get a picture of a random cat 2. In `exports.handler`, create a new variable named `catUrl`, and call the getCat function with the argument of `requestObj.category` 3. Make sure that you replace the first argument in calling `manipulateImage` with `catUrl` The code to return a cat with a tag is: ```js function getCat(tag) { // if a tag is provided, use that tag to return an image of that sort. If not, return a random cat pic if (tag) { return `https://cataas.com/cat/${tag}` } return `https://cataas.com/cat` } ``` > Since an empty string evaluates to `false` in JavaScript, we will only return a cat with a tag if the category is defined, and not an empty string. #### ๐Ÿ” Implementing rotate 1. Add an `if-statement` to check whether or not the rotate `queryStringParameter` was passed in the `manipulateImage` function if it was, rotate the image by the number of degrees specified in `parseInt(requestObj.rotate)` ```js if (requestObj.rotate) { const rotateDeg = parseInt(requestObj.rotate); await image.rotate(rotateDeg); } ``` > An empty string evaluates to false in JavaScript, so the code inside the if will not run if there is no rotate degrees pased in! ![](https://i.imgur.com/DDsbNTS.png) #### ๐Ÿ’ซ Implementing flip Add an `if-else statement` to check whether or not the `requestObj.flip` is set to `h`, `v`, or `b`. - `v` stands for vertical flip - `h` stands for horizontal flip - `b` stands for a flip in both directions ```js if (requestObj.flip === "h") { wait image.flip(true, false); // flips the image horizontally } else if (requestObj.flip === "v") { await image.flip(false, true); // flips the image vertically } else if (requestObj.flip === "b") { await image.flip(true, true); // flips the image both horizontally and vertically } ``` ### ๐Ÿฑ Testing: Let's deploy this function and test it on postman to see if it actually works! > When testing the function now, you will need to specify request parameters, but you can leave their value blank. ![](https://i.imgur.com/pcFULyt.png) #### ๐Ÿ”€ Randomizing all categories What if the user wanted all random categories? We can do this by checking if the request's `random` parameter is set to `true` In our exports.handler, add this right after creating `requestObj` ```js if (requestObj.random === "true") { requestObj.category = ""; requestObj.text = randomText(); // get a random text requestObj.rotate = randomNumber(0, 360); // rotate a random number of degrees requestObj.flip = Math.random() > 0.5 ? "x" : "y"; // random flip in either x or y } ``` > โ—โ— don't run this yet!!! we still need to declare the randomtext() and randomNumber() functions! #### randomText() We will create an array of possible texts, and choose a random item from that array ```js const puns = [ย "Purrfect", "Poo Poo", "Kitty!", "Paw-some."] return puns[Math.floor(Math.random()*puns.length)]; // Math.random() returns a decimal # from 0 to 1 (non-inclusive of 1) ``` > You can replace the words in the array `puns` with anything you'd like! #### randomNumber(min, max) Create a function that returns a random number given a min and max value ```js function randomNumber(min, max) { return Math.floor(Math.random() * max) + min } ``` > This function returns an integer ranging from the `min` value passed in, to the `max` value parameter. ### ๐Ÿงช Test it out in postman and use the `random` paramter set to true! ![](https://i.imgur.com/amA8Aha.png) #### ๐ŸŒ Enabling CORS Cross-Origin Resource Sharing (CORS) allows a server to indicate any other domains than its own from which a browser should allow the request to pass. > Basically CORS is what determines whether or not we are able to call our API from another website! Since we will be calling our AWS Lambda function from another domain (replit.co), we will need to enable CORS! In the return statement of the `exports.handler`, we need to add some headers that say "i want to be able to send info to any other website" We also need to return the base64 in a stringified JSON. ```js return { headers: { "Access-Control-Allow-Headers" : "Content-Type", "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "OPTIONS,POST,GET" }, body: JSON.stringify({base64Str}) }; ``` - `Access-Control-Allow-Headers` tells the server which HTTP headers can be used when making a request. - `Access-Control-Allow-Origin` tells the server which domains (such as www.example.com) to allow the request to come from. - `Access-Control-Allow-Methods` tells the server which methods to allow, such as a `GET` or `POST` request. ### ๐Ÿ‘— Our backend is now done! Let's create the frontend of Catti 1. Download this [zip file](https://github.com/ganning127/catti/blob/main/catti-frontend.zip), and unzip it! 2. Go to `replit.com` and sign up for an account (or login if you already have one) 3. Click `new repl` and under languages, select `HTML, CSS, and JS` 4. Click upload file, and click into the unzipped folder, and select `index.html`, `index.css`, and `index.js` 5. Click upload folder option and upload the img folder > If replit prompts you asking if files will be replaced, just click confirm. ### ๐Ÿˆ Updating the JS 1. Grab your AWS gateway URL, and click into index.js 2. Scroll down to line 56, and replace everything before the last question mark with your endpoint 3. Click run at the top, and watch the magic happen! And that's it! We just created [Catti](https://ganning127.github.io/catti/)! ![](https://i.imgur.com/4cdnRsF.jpg) If you fell behind, not worries, you can find the source code on my [GitHub](https://github.com/ganning127/catti/blob/main/aws-lambda-code.js)