The Quest for Serverless

A presentation at Fauna DB All Hands in July 2019 in Boston, MA, USA by Divya

Slide 1

Slide 1

The Quest for Serverless Divya Sasidharan

Slide 2

Slide 2

HELLO MY NAME IS Hellot Divya Sasidharan

Slide 3

Slide 3

HELLO MY NAME IS Divya Sasidharan Hellot @shortdiv

Slide 4

Slide 4

Slide 5

Slide 5

Slide 6

Slide 6

Slide 7

Slide 7

The Ordinary World The call to adventure Refusal Meeting the Mentor Crossing the threshold Tests, Allies, Enemies Innermost Cave Ordeal Reward The road back Resurrection Return with the Elixir

Slide 8

Slide 8

The Ordinary World The call to adventure Refusal Meeting the Mentor Crossing the threshold Tests, Allies, Enemies Innermost Cave Ordeal Reward The road back Resurrection Return with the Elixir DEPARTURE

Slide 9

Slide 9

The Ordinary World The call to adventure Refusal DEPARTURE Meeting the Mentor Crossing the threshold Tests, Allies, Enemies Innermost Cave Ordeal Reward The road back Resurrection Return with the Elixir INITIATION

Slide 10

Slide 10

The Ordinary World The call to adventure Refusal DEPARTURE Meeting the Mentor Crossing the threshold Tests, Allies, Enemies Innermost Cave INITIATION Ordeal Reward The road back Resurrection Return with the Elixir RETURN

Slide 11

Slide 11

Act 1 Departure

Slide 12

Slide 12

The Ordinary World DATE SCENE ACT 07/09 1 1

Slide 13

Slide 13

The Ordinary World DATE SCENE ACT 07/09 1 1

Slide 14

Slide 14

The Ordinary World DATE SCENE ACT 07/09 1 1

Slide 15

Slide 15

✅ Sammy Serverus

Slide 16

Slide 16

bon appetite Spatchcocked Chicken The best recipe for those of you living under a rock

Slide 17

Slide 17

Slide 18

Slide 18

Slide 19

Slide 19

Slide 20

Slide 20

Call to Adventure DATE SCENE ACT 07/09 2 1

Slide 21

Slide 21

Call to Adventure DATE SCENE ACT 07/09 2 1

Slide 22

Slide 22

Call to Adventure DATE SCENE ACT 07/09 2 1

Slide 23

Slide 23

Orange News 1. Some news (github.com) 100000 points by somedude 3 hours ago | hide | 100000 comments 2. Some news (github.com) 100000 points by somedude 3 hours ago | hide | 1 comments 3. Some news (github.com) 100000 points by somedude 3 hours ago | hide | 100000 comments 4. Some news (github.com) 100000 points by somedude 3 hours ago | hide | 100000 comments 5. Some news (github.com) 100000 points by somedude 3 hours ago | hide | 100000 comments 6. 7. 8. 9. 10. 11. 12. 13. Some news (github.com) 100000 points by somedude 3 hours ago | hide | 100000 comments Some news (github.com) 100000 points by somedude 3 hours ago | hide | 100000 comments Some news (github.com) 100000 points by somedude 3 hours ago | hide | 100000 comments Some news (github.com) 100000 points by somedude 3 hours ago | hide | 100000 comments Some news (github.com) 100000 points by somedude 3 hours ago | hide | 100000 comments Some news (github.com) 100000 points by somedude 3 hours ago | hide | 100000 comments Some news (github.com) 100000 points by somedude 3 hours ago | hide | 100000 comments Some news (github.com) 100000 points by somedude 3 hours ago | hide | 100000 comments 14. Some news (github.com) 100000 points by somedude 3 hours ago | hide | 100000 comments 15. Some news (github.com)

Slide 24

Slide 24

Slide 25

Slide 25

got-lobstah.com

Slide 26

Slide 26

Slide 27

Slide 27

How ‘bout that “JAM“ I’ll lose the bleeps, the sweeps and all those creeps

Slide 28

Slide 28

Refusal DATE SCENE ACT 07/09 3 1

Slide 29

Slide 29

Refusal DATE SCENE ACT 07/09 3 1

Slide 30

Slide 30

Refusal DATE SCENE ACT 07/09 3 1

Slide 31

Slide 31

We’re gonna need a bigger server rack

Slide 32

Slide 32

Slide 33

Slide 33

Slide 34

Slide 34

Slide 35

Slide 35

Slide 36

Slide 36

😩😩😩😩

Slide 37

Slide 37

Slide 38

Slide 38

Slide 39

Slide 39

Meeting the Mentor DATE SCENE ACT 07/09 4 1

Slide 40

Slide 40

Meeting the Mentor DATE SCENE ACT 07/09 4 1

Slide 41

Slide 41

Meeting the Mentor DATE SCENE ACT 07/09 4 1

Slide 42

Slide 42

Swami Scale-a-lot

Slide 43

Slide 43

Man who run servers without servers, accomplish many things

Slide 44

Slide 44

Crossing the Threshold DATE SCENE ACT 07/09 5 1

Slide 45

Slide 45

Crossing the Threshold DATE SCENE ACT 07/09 5 1

Slide 46

Slide 46

Crossing the Threshold DATE SCENE ACT 07/09 5 1

Slide 47

Slide 47

Slide 48

Slide 48

Slide 49

Slide 49

Slide 50

Slide 50

Act 2 Initiation

Slide 51

Slide 51

Tests, Allies, Enemies DATE SCENE ACT 07/09 1 2

Slide 52

Slide 52

Tests, Allies, Enemies DATE SCENE ACT 07/09 1 2

Slide 53

Slide 53

Tests, Allies, Enemies DATE SCENE ACT 07/09 1 2

Slide 54

Slide 54

|—————-| | THERE | | ARE | | STILL | | SERVERS | | IN | | SERVERLESS| |—————-| (__/) || (•ㅅ•) || / づ

Slide 55

Slide 55

How ‘bout that “Cold Start“?

Slide 56

Slide 56

Slide 57

Slide 57

Innermost Cave DATE SCENE ACT 07/09 2 2

Slide 58

Slide 58

Innermost Cave DATE SCENE ACT 07/09 2 2

Slide 59

Slide 59

Innermost Cave DATE SCENE ACT 07/09 2 2

Slide 60

Slide 60

Slide 61

Slide 61

Slide 62

Slide 62

Slide 63

Slide 63

1 const express = require(‘express’), yelp = require(‘yelp-fusion’), 2 port = 8000, 3 app = express(); 4 5 6 app.use((req, res, next) => { res.header(“Access-Control-Allow-Origin”, “*”); 7 res.header(“Access-Control-Allow-Headers”, “Origin, X-Requested 8 -with, Content-Type, Accept”); 9 next(); 10 }) 11 12 13 app.get(‘/yelpit/:term/:location’, (req, res) => { const clientId = CLIENT_ID; 14 const clientSecret = CLIENT_SECRET; 15 16 yelp.accessToken(clientId, clientSecret) 17 .then((response) => { 18 const client = yelp.client(response.jsonBody.access_token); 19 const searchRequest = { 20 term: req.params.term, 21 limit: 50, 22 location: req.params.location 23 } 24 client.search(searchRequest).then(response => { 25 const firstResult = response.jsonBody.businesses; 26 res.send(firstResult) 27

Slide 64

Slide 64

1 const express = require(‘express’), yelp = require(‘yelp-fusion’), 2 port = 8000, 3 app = express(); 4 5 6 app.use((req, res, next) => { res.header(“Access-Control-Allow-Origin”, “*”); 7 res.header(“Access-Control-Allow-Headers”, “Origin, X-Requested 8 -with, Content-Type, Accept”); 9 next(); 10 }) 11 12 13 app.get(‘/yelpit/:term/:location’, (req, res) => { const clientId = CLIENT_ID; 14 const clientSecret = CLIENT_SECRET; 15 16 yelp.accessToken(clientId, clientSecret) 17 .then((response) => { 18 const client = yelp.client(response.jsonBody.access_token); 19 const searchRequest = { 20 term: req.params.term, 21 limit: 50, 22 location: req.params.location 23 } 24 client.search(searchRequest).then(response => { 25 const firstResult = response.jsonBody.businesses; 26 res.send(firstResult) 27

Slide 65

Slide 65

10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 }) app.get(‘/yelpit/:term/:location’, (req, res) => { const clientId = CLIENT_ID; const clientSecret = CLIENT_SECRET; }) yelp.accessToken(clientId, clientSecret) .then((response) => { const client = yelp.client(response.jsonBody.access_token); const searchRequest = { term: req.params.term, limit: 50, location: req.params.location } client.search(searchRequest).then(response => { const firstResult = response.jsonBody.businesses; res.send(firstResult) }) }) app.listen(port, ‘localhost’, function(err) { if(err) {console.log(err)} console.info(Listening on port ${port}) });

Slide 66

Slide 66

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 module.exports = { devServer: { proxy: { ‘^/yelpit/*’: { target: ‘http://localhost:8000/yelpit’, secure: false } } } }

Slide 67

Slide 67

The Chilling Adventures of Functions as a Service |—————-| | THERE | | ARE | | STILL | | SERVERS | | IN | | SERVERLESS| |—————-| (_/) || (•ㅅ•) || / づ |—————-| | THERE | | ARE | | STILL | | SERVERS | | IN | | SERVERLESS| |—————-| (_/) || (•ㅅ•) || / づ |—————-| | THERE | | ARE | | STILL | | SERVERS | | IN | | SERVERLESS| |—————-| (_/) || (•ㅅ•) || / づ |—————-| | THERE | | ARE | | STILL | | SERVERS | | IN | | SERVERLESS| |—————-| (_/) || (•ㅅ•) || / づ

Slide 68

Slide 68

Slide 69

Slide 69

exports.handler = function(event, context, callback) { console.log(event) callback(null, { statusCode: 200, body: JSON.stringify({ msg: “Hello, World!” }) }) } functions/hello.js

Slide 70

Slide 70

[build] command = “yarn build” functions = “functions” publish = “dist” netlify.toml

Slide 71

Slide 71

Slide 72

Slide 72

/.netlify/functions/yelp-it

Slide 73

Slide 73

1 const yelp = require(“yelp-fusion”); 2 3 exports.handler = async event => { 4 const client = yelp.client(process.env.YELP_API_KEY); 5 const { term, location } = event.queryStringParameters; 6 const searchRequest = { 7 term: term, 8 location: location 9 }; 10 var geojsonify = async res => { 11 let features = []; 12 res.map(item => { 13 features.push({ 14 type: “Feature”, 15 geometry: { 16 type: “Point”, 17 coordinates: [item.coordinates.longitude, item.coordinates.l 18 }, 19 properties: { 20 …item 21 22 } 23 }); 24 }); 25 return { 26 type: “FeatureCollection”, 27 features 28 }; 29 };

Slide 74

Slide 74

coordinates: [item.coordinates.longitude, item.coordinates.l 16 }, 17 properties: { 18 …item 19 } 20 }); 21 }); 22 return { 23 type: “FeatureCollection”, 24 25 features 26 }; 27 }; 28 try { 29 let res = await client.search(searchRequest); 30 let geojsonified = await geojsonify(res.jsonBody.businesses); 31 return { 32 statusCode: 200, 33 body: JSON.stringify({ 34 results: geojsonified 35 }) 36 }; 37 } catch (err) { 38 return { statusCode: 500, body: err.toString() }; 39 } 40 41 }; 42 43 44

Slide 75

Slide 75

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <template> <div class=“home”> <div id=”sidebar” v-if=”dataLoaded”> <Slider :yelpData=“yelpData.features” /> </div> </div> </template>

<script> import { value, onCreated } from “vue-function-api”; export default { name: “home”, setup() { const yelpData = value(null); const getYelpData = () => { axios .get(“/.netlify/functions/yelp-it”, { params: { location: “boston, ma”, term: “lobster” } }) .then(async res => { yelpData.value = res.data.results;

Slide 76

Slide 76

14 export default { name: “home”, 15 setup() { 16 const yelpData = value(null); 17 const getYelpData = () => { 18 axios 19 .get(“/.netlify/functions/yelp-it”, { 20 params: { 21 location: “boston, ma”, 22 term: “lobster” 23 } 24 }) 25 .then(async res => { 26 yelpData.value = res.data.results; 27 }) 28 .catch(err => { 29 console.log(err); 30 }); 31 }; 32 onCreated(async () => { 33 getYelpData(); 34 }); 35 } 36 37 } 38 </script> 39 40

Slide 77

Slide 77

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <template> <div class=“home”> <div id=”sidebar” v-if=”dataLoaded”> <Slider :yelpData=“yelpData.features” /> </div> </div> </template>

<script> import { value, onCreated } from “vue-function-api”; export default { name: “home”, setup() { const yelpData = value(null); const getYelpData = () => { axios .get(“/.netlify/functions/yelp-it”, { params: { location: “boston, ma”, term: “lobster” } }) .then(async res => { yelpData.value = res.data.results;

Slide 78

Slide 78

Slide 79

Slide 79

Slide 80

Slide 80

Slide 81

Slide 81

netlify addons:create fauna

Slide 82

Slide 82

1 const faunadb = require(“faunadb”); 2 const q = faunadb.query; 3 4 export default async function useFauna() { const createClient = async () => { 5 const secret = await axios.get(“/.netlify/functions/fauna-user” 6 const { faunaSecret } = secret.data; 7 const client = new faunadb.Client({ secret: faunaSecret }); 8 return client; 9 }; 10 11 const client = await createClient(); 12 13 const setRating = ref => async title => { 14 try { 15 if (ref === null) { 16 client 17 .query( 18 q.Create(q.Class(“ratings”), { 19 data: title 20 }) 21 ) 22 .then(ret => console.log(ret)) 23 .catch(err => console.log(err)); 24 } 25 const listicle = await client 26

Slide 83

Slide 83

12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 const client = await createClient(); const setRating = ref => async title => { try { if (ref === null) { client .query( q.Create(q.Class(“ratings”), { data: title }) ) .then(ret => console.log(ret)) .catch(err => console.log(err)); } else { client .query( q.Update(q.Ref(q.Class(“ratings”), ref), { data: title }) ) .then(ret => console.log(ret)); } return listicle; } catch (err) { console.log(err); } };

Slide 84

Slide 84

37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 } 61 62 console.log(err); } }; const getRatings = async () => { try { const res = await client.query( q.Map(q.Paginate(q.Match(q.Ref(“indexes/all_ratings”))), ref => q.Get(ref) ) ); return res.data; } catch (err) { console.log(err); } }; return { client, getRatings, setRating };

Slide 85

Slide 85

Ordeal DATE SCENE ACT 07/09 3 2

Slide 86

Slide 86

Ordeal DATE SCENE ACT 07/09 3 2

Slide 87

Slide 87

Ordeal DATE SCENE ACT 07/09 3 2

Slide 88

Slide 88

Slide 89

Slide 89

Auth as a Service

Slide 90

Slide 90

Netlify Identity

Slide 91

Slide 91

Slide 92

Slide 92

Slide 93

Slide 93

1 const netlifyIdentity = require(“netlify-identity-widget”); C 2 3 export default function useAuth() { 4 const netlifyIdentityInit = () => { 5 return netlifyIdentity.init({ 6 container: “.account-profile” C 7 }); 8 }; 9 10 netlifyIdentityInit(); 11 12 return { 13 netlifyIdentity 14 15 }; 16 }; 17 18 19 20 21 22 23 24 25 26 27 28

Slide 94

Slide 94

/.netlify/functions/handle-signin

Slide 95

Slide 95

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 ‘use strict’; const faunadb = require(‘faunadb’); C const generator = require(‘generate-password’); /* configure faunaDB Client with our secret / const q = faunadb.query; C const client = new faunadb.Client({ secret: process.env.FAUNADB_SERVER_SECRET C }); / create a user in FaunaDB that can connect from the browser */ function createUser(userData, password) { return client.query( q.Create(q.Class(‘users’), { credentials: { password: password }, data: { id: userData.id, user_metadata: userData.user_metadata } }) ); } function obtainToken(user, password) { return client.query(q.Login(q.Select(‘ref’, user), { password })); }

Slide 96

Slide 96

return client.query(q.Login(q.Select(‘ref’, user), { password })); 29 } 30 31 export function handler(event, context, callback) { C 32 var payload = JSON.parse(event.body); C 33 var userData = payload.user; C 34 35 const password = generator.generate({ 36 length: 10, 37 C numbers: true 38 }); 39 40 createUser(userData, password)C 41 .then(user => obtainToken(user, password)) C 42 .then(key => 43 callback(null, { 44 statusCode: 200, 45 body: JSON.stringify({ 46 app_metadata: { 47 faunadb_token: key.secret C 48 } 49 }) 50 }) 51 ) 52 .catch(e => { 53 console.error(e); 54 callback(null, { 55 statusCode: 500, 56 body: JSON.stringify({ 57 error: e

Slide 97

Slide 97

Reward DATE SCENE ACT 07/09 4 2

Slide 98

Slide 98

Reward DATE SCENE ACT 07/09 4 2

Slide 99

Slide 99

Reward DATE SCENE ACT 07/09 4 2

Slide 100

Slide 100

Slide 101

Slide 101

JAMstack ecosystem

Slide 102

Slide 102

Form Handling Search Push Notifications Authentication Database Static Hosting Payments Media Storage Headless CMS Monitoring FAAS Providers JAMstack ecosystem

Slide 103

Slide 103

secure fast flexible scalable

Slide 104

Slide 104

Act 3 Return

Slide 105

Slide 105

The Road Back DATE SCENE ACT 07/09 1 3

Slide 106

Slide 106

The Road Back DATE SCENE ACT 07/09 1 3

Slide 107

Slide 107

The Road Back DATE SCENE ACT 07/09 1 3

Slide 108

Slide 108

There is no cloud, just someone else’s server

Slide 109

Slide 109

Resurrection/ Return with Elixir DATE SCENE ACT 07/09 2 3

Slide 110

Slide 110

Resurrection/ Return with Elixir DATE SCENE ACT 07/09 2 3

Slide 111

Slide 111

Resurrection/ Return with Elixir DATE SCENE ACT 07/09 2 3

Slide 112

Slide 112

thepowerofserverless.info

Slide 113

Slide 113

Slide 114

Slide 114

all the things

Slide 115

Slide 115

👏 👏 👏 👏 👏 👏 ⚡ ⚡ ⚡ 👏 👏 👏 👏

Slide 116

Slide 116

gotlobstah.netlify.com

Slide 117

Slide 117

% ❤ github.com/shortdiv/got-lobstah