Serverless Functions and Vue.js

A presentation at Serverless Days London in July 2018 in London, UK by Sarah Drasner

Slide 1

Slide 1

SERVERLESS FUNCTIONS AND VUE.JS

Slide 2

Slide 2

Slide 3

Slide 3

SUCCESS!

Slide 4

Slide 4

Slide 5

Slide 5

Slide 6

Slide 6

NOT FAIL…

Slide 7

Slide 7

Slide 8

Slide 8

SERVERLESS AN ACTUALLY INTERESTING THING WITH A CLICKBAITY TITLE FaaS FUNCTIONS AS A SERVICE

Slide 9

Slide 9

SARAH DRASNER @SARAH_EDO

Slide 10

Slide 10

BENEFITS • You pay only for what you use (usually a lot less) • Less time babysitting a server • Same idea as functional programming, breaking things into small bits • Principle of least power

Slide 11

Slide 11

LINK TO CODEPEN

Slide 12

Slide 12

LINK TO CODEPEN

Slide 13

Slide 13

Slide 14

Slide 14

Slide 15

Slide 15

SERVERLESS ALL THE THINGS? NO, NOT REALLY.

Slide 16

Slide 16

SO WHAT IS IT GOOD FOR? • Clean data on a cron job ⏰

• Take data and use it to create data visualizations "

• Crop images uploaded by the user and create a user profile

Slide 17

Slide 17

Consumption Plan Free account signup

Slide 18

Slide 18

Slide 19

Slide 19

VUE.JS, STRIPE, AND SERVERLESS FIRST USE CASE

Slide 20

Slide 20

SAMPLE VUE SHOP VISIT THE SITE

Slide 21

Slide 21

THE APPLICATION VISIT THE REPO

Slide 22

Slide 22

UNDER API

Slide 23

Slide 23

app.get( '/' , (req, res) => res.render( 'index.pug' , { keyPublishable })); app.post( '/charge' , (req, res) => {

let amount = 500 ; stripe.customers .create({ email: req.body.stripeEmail, source: req.body.stripeToken }) .then(customer => stripe.charges.create({ amount, description: 'Sample Charge' , currency: 'usd' , customer: customer.id }) ) .then(charge => res.render( 'charge.pug' )); }); app.listen( 4567 ); What Stripe expects: Express

Slide 24

Slide 24

Where we’re going we don’t need Express!

Slide 25

Slide 25

Kick things off! var stripe = require ( ‘stripe' )( 'sk_test_XXXXXXXXXXXXXXXX' ); // ^ this is a stripe testing key module .exports = function (context, req) { context.log( 'starting to get down' );

Slide 26

Slide 26

If we have what we need,
create the customer and then the charge if ( req.body && req.body.stripeEmail && req.body.stripeToken && req.body.stripeAmt ){ stripe.customers .create({ email: req.body.stripeEmail, source: req.body.stripeToken }) .then(customer => { context.log( 'starting the stripe charges' ); stripe.charges.create({ amount: req.body.stripeAmt, description: 'Sample Charge' , currency: 'usd' , customer: customer.id }); }) ...

Slide 27

Slide 27

Then finish, otherwise error logs ... .then(charge => { context.log( 'finished the stripe charges' ); context.res = {

// status: 200 body: 'This has been completed' }; context.done(); }) .catch(err => { context.log(err); context.done(); }); } else { context.log(req.body); context.res = { status: 400 , body: "We're missing something" }; context.done(); } };

Slide 28

Slide 28

Slide 29

Slide 29

SUCCESS! POST ON CSS-TRICKS

Slide 30

Slide 30

Slide 31

Slide 31

Slide 32

Slide 32

OUR CART VUEX HAS A MANIFEST OF ALL OF OUR PRODUCTS, ITEMS, AND TOTAL

Slide 33

Slide 33

< div v-if= "cartTotal > 0"

<!--we'll add our checkout here-->

</ div

< div v-else-if= "cartTotal === 0 && success === false" class= "empty"

<!--we'll add our empty state here-->

</ div

< div v-else>

<!--we'll add success here-->

</ div

Cart.vue Base Logic

Slide 34

Slide 34

OUR CART < div v-if= "cartTotal > 0"

< h1

Cart </ h1

< div class= "cartitems" v-for= "item in cart" key= "item"

< div class= "carttext"

< h4

{{ item.name }} </ h4

< p

{{ item.price | usdollar }} x {{ item.count }} </ p

< p

Total for this item: < strong

{{ item.price * item.count }} </ strong

</ p

</ div

< img class= "cartimg" :src= "/${item.img}" :alt= "Image of ${item.name}"

</ div

< div class= "total"

< h3

Total: {{ total | usdollar }} </ h3

</ div

<!--we're going to add our checkout here-->

</ div

Slide 35

Slide 35

computed: { ... total() {

return

Object .values( this .cart) .reduce((acc, el) => acc + (el.count * el.price), 0 ) .toFixed( 2 ); } }

Slide 36

Slide 36

Slide 37

Slide 37

VUE STRIPE CHECKOUT EASIEST WAY REPO

Slide 38

Slide 38

STRIPE ELEMENTS WE’LL USE:

Slide 39

Slide 39

OR npm i vue-stripe-elements-plus yarn add vue-stripe-elements-plus

Slide 40

Slide 40

Slide 41

Slide 41

DEFAULT STRIPE-ELEMENT < template

< div id= 'app'

< h1

Please give us your payment details: </ h1

< card class= 'stripe-card' :class= '{ complete }' stripe= 'pk_test_XXXXXXXXXXXXXXXXXXXXXXXX' :options= 'stripeOptions' @change= 'complete = $event.complete' />

< button class= 'pay-with-stripe'

  @click=

'pay'

  :disabled=

'!complete' > Pay with credit card

</ button

</ div

</ template

Slide 42

Slide 42

DEFAULT STRIPE-ELEMENT import { stripeKey, stripeOptions } from

'./stripeConfig.json' import { Card, createToken } from

'vue-stripe-elements-plus' export

default { data () {

return { complete: false , stripeOptions: {

// see https://stripe.com/docs/stripe.js#element-options for details } } }, components: { Card }, methods: { pay() {

// createToken returns a Promise which resolves in a result object with

// either a token or an error key. createToken().then(data => console .log(data.token)) } } }

Slide 43

Slide 43

WHAT WE’LL NEED: data() {

return { submitted: false , complete: false , status: '' , response: '' , stripeOptions: {

// you can configure that cc element. }, stripeEmail: '' } },

Slide 44

Slide 44

WHAT WE’LL NEED: props: { total: { type: [ Number , String ], default: '50.00' }, success: { type: Boolean , default: false } },

Slide 45

Slide 45

HERE WE GO! pay() { createToken().then(data => {

// we'll change this to know it was submitted

this .submitted = true ;

// this is a token we would use for the stripeToken below

console .log(data.token); axios .post(

'https://sdras-stripe.azurewebsites.net/api/charge? code=zWwbn6LLqMxuyvwbWpTFXdRxFd7a27KCRCEseL7zEqbM9ijAgj1c1w==' , { stripeEmail: this .stripeEmail, // send the email stripeToken: data.token.id, // testing token stripeAmt: this .total // send the amount }, { headers: {

'Content-Type' : 'application/json' } } ) ...

Slide 46

Slide 46

Slide 47

Slide 47

HERE WE GO! ... .then(response => {

this .status = 'success' ;

this .$emit( 'successSubmit' );

this .$store.commit( 'clearCartCount' );

// console logs for you :)

this .response = JSON .stringify(response, null , 2 );

console .log( this .response); }) .catch(error => {

this .status = 'failure' ;

// console logs for you :)

this .response = 'Error: ' + JSON .stringify(error, null , 2 );

console .log( this .response); }); });

Slide 48

Slide 48

pay() • Uses Axios to post to our function to our function URL • Tracks whether we've submitted the form or not, with this.submitted

• It sends the email, token, and total to our serverless function • If it's successful, commits to our Vuex store, mutation clears our cart, and emits to the parent cart component that the payment was successful.  

• If it errors, it changes the status to failure, and logs the error response for help with debugging

Slide 49

Slide 49

WHILE THE FUNCTION WORKS ITS MAGIC ✨ LINK TO CODEPEN

Slide 50

Slide 50

IN THE CASE OF FAILURE

Slide 51

Slide 51

Template Script < div v-if= "status === 'failure'"

< h3

Oh No! </ h3

< p

Something went wrong! </ p

< button @click= "clearCheckout"

Please try again </ button

</ div

clearCheckout() {

this .submitted = false ;

this .status = '' ;

this .complete = false ;

this .response = '' ; }

Slide 52

Slide 52

IN THE CASE OF SUCCESS

Slide 53

Slide 53

Slide 54

Slide 54

Slide 55

Slide 55

AppSuccess.vue window .setTimeout(() => this .$emit( 'restartCart' ), 3000 ); THIS CODEPEN

Slide 56

Slide 56

< div v-if= "cartTotal > 0"

<!--we'll add our checkout here-->

</ div

< div v-else-if= "cartTotal === 0 && success === false" class= "empty"

<!--we'll add our empty state here-->

</ div

< div v-else>

<!--we'll add success here-->

</ div

Slide 57

Slide 57

< div v-if= "cartTotal > 0"

< h1

Cart </ h1

...

< app-checkout :total= "total" @successSubmit= "success = true"

</ app-checkout

</ div

< div v-else-if= "cartTotal === 0 && success === false" class= "empty"

< h1

Cart </ h1

< h3

Your cart is empty. </ h3

< nuxt-link exact to= "/"

< button

Fill 'er up! </ button

</ nuxt-link

</ div

< div v-else>

< app-success @restartCart= "success = false" />

< h2

Success! </ h2

< p

Your order has been processed, it will be delivered shortly. </ p

</ div

Slide 58

Slide 58

Slide 59

Slide 59

SECOND USE CASE CREATE AN API FOR A VUE APP API FOR SERVERLESS FUNCTIONS

Slide 60

Slide 60

Slide 61

Slide 61

Ice Cream %

Slide 62

Slide 62

Puppy &

Slide 63

Slide 63

Things we like ❤

Slide 64

Slide 64

MAKE THE NEW FUNCTION

Slide 65

Slide 65

Slide 66

Slide 66

• Give the route template a name • Authorization level: Anonymous • Allowed HTTP methods: Selected Method • Select GET 
 In Integrate:

Slide 67

Slide 67

Slide 68

Slide 68

Slide 69

Slide 69

Slide 70

Slide 70

Slide 71

Slide 71

Slide 72

Slide 72

Slide 73

Slide 73

{

"bindings" : [ {

"authLevel" : " function " ,

"type" : " httpTrigger " ,

"direction" : " in " ,

"name" : " req "

}, 
{ 
  

"type" : " http " ,

"direction" : " out " ,

"name" : " res "

} 

],

"disabled" : false

}

Slide 74

Slide 74

{

"bindings" : [ {

"authLevel" : " anonymous " ,

"type" : " httpTrigger " ,

"direction" : " in " ,

"name" : " req " ,

"route" : " hello " ,

"methods" : [

" get "

  ] 
}, 
{ 
  

"type" : " http " ,

"direction" : " out " ,

"name" : " res "

}, 
{ 
  

"type" : " documentDB " ,

"name" : " inputDocument " ,

"databaseName" : " CocktailFinder " ,

"collectionName" : " Cocktails " ,

"partitionKey" : " /id " ,

"connection" : " sdrasbootcamp_DOCUMENTDB " ,

"direction" : " in "

} 

],

"disabled" : false

}

Slide 75

Slide 75

{

… {

"type" : " documentDB " ,

"name" : " inputDocument " ,

"databaseName" : " CocktailFinder " ,

"collectionName" : " Cocktails " ,

"partitionKey" : " /id " ,

"connection" : “ sdrasapidemo_DOCUMENTDB " ,

"direction" : " in "

} 

], … }

Slide 76

Slide 76

module. exports

=

function ( context , req ) { context . log ( ' JavaScript HTTP trigger function processed a request. ' );

if (req .query.name

|| (req .body

&& req .body.name )) { context .res

= {

// status: 200, !" Defaults to 200 #$

body : " Hello

"

(req .query.name

|| req .body.name ) }; }

else { context .res

= {

status : 400 ,

body : " Please pass a name on the query string or in the request body "

}; 

} context . done (); };

Slide 77

Slide 77

module. exports

=

function ( context , req ) { context . log ( ' JavaScript HTTP trigger function processed a request. ' );

if (context .bindings ) { context . log ( ' Get ready ' ); context .res

= { status : 200 , body : context .bindings.inputDocument }; context . log (context .res ); }

else { context .res

= {

status : 400 ,

body : " Something went wrong "

}; 

} context . done (); };

Slide 78

Slide 78

Slide 79

Slide 79

https://bit.ly/2KB13pA

Slide 80

Slide 80

SUCCESS!

Slide 81

Slide 81

Slide 82

Slide 82

export

default {

created () {

this.axios

. get (

https://sdras-api-demo.azurewebsites.net/api/hello

  ) 
  

. then ( response

!" {

this.info

= response .data.body

  }) 

} }

pages/Item.vue

Slide 83

Slide 83

<template> <main> <header class = " header " > <h1> {{ info .drink }} </h1>

<icon-switcher :glass

“ info .glass[0] ”

class

" icon-switcher " />

</header> <article class = " content " > <h4> {{ info .category }} </h4> <div class = " ingredient-list " > <ul> <li v-for = " i in measure "

:key

" i "

{{ i }}

</li> </ul> <ul> <li v-for = " i in ingredient "

:key

" i "

{{ i }}

</li> </ul> </div> <p> {{ info .description }} </p>

<nuxt-link to

" / "

exact

<v-btn outline

color

" white "

Back to Search </v-btn></nuxt-link>

</article> <aside class = " sidebar " ><img :src = " info .strDrinkThumb "

width

" 100% " /></aside>

</main> </template> components/AppDrinkSingle.vue

Slide 84

Slide 84

Slide 85

Slide 85

THIRD USE CASE LET’S GET VISUAL GOOGLE MAPS API POWERED DATA VIS

Slide 86

Slide 86

Series:   Exploring data with Serverless and Vue DEMO   AND   REPO W/ OSS CODE

Slide 87

Slide 87

[
{

" Name " : " Simona Cotin " ,

" Conference " : " DLD 2017 Tel Aviv Israel " ,

" From " : " 9/4/2017 " ,

" To " : " 9/7/2017 " ,

" Location " : " Tel Aviv " ,

" Link " : “"

},

...

]

What we start with looks like this

Slide 88

Slide 88

MAKE THE FUNCTION

Slide 89

Slide 89

getGeo(makeIterator(content), (updatedContent, err) => {

if (!err) {

// we need to base64 encode the JSON to embed it into the PUT (dear god, why)

let updatedContentB64 = new Buffer(

JSON .stringify(updatedContent, null , 2 ) ).toString( 'base64' );

let pushData = { path: GH_FILE, message: 'Looked up locations, beep boop.' , content: updatedContentB64, sha: data.sha }; ... }; We're going to retrieve the geo-information for each item in the original data

Slide 90

Slide 90

function

getGeo (itr, cb) {

let curr = itr.next();

if (curr.done) {

// All done processing- pass the (now-populated) entries to the next callback cb(curr.data);

return ; }

let location = curr.value.Location;

Slide 91

Slide 91

Slide 92

Slide 92

[
{

" Name " : " Simona Cotin " ,

" Conference " : " DLD 2017 Tel Aviv Israel " ,

" From " : " 9/4/2017 " ,

" To " : " 9/7/2017 " ,

" Location " : " Tel Aviv " ,

" Link " : “"

},

...

]

The function updates our cda-data.json… …which we pull into our Vuex store

Slide 93

Slide 93

[
{

" Name " : " Simona Cotin " ,

" Conference " : " DLD 2017 Tel Aviv Israel " ,

" From " : " 9/4/2017 " ,

" To " : " 9/7/2017 " ,

" Location " : " Tel Aviv " ,

" Link " : "" ,

" Latitude " : 32.0852999 ,

" Longitude " : 34.78176759999999

},

...

]

The function updates our cda-data.json… …which we pull into our Vuex store

Slide 94

Slide 94

computed: {

speakerData () {

return

this . $store . state . speakerData ; },

filteredData () {

const x

this . selectedFilter , filter

new

RegExp ( this . filteredText , ' i ' )

return

this . speakerData . filter ( el

=> {

if (el[x] !==

undefined ) { return el[x] . match (filter) }

else

return

true ; }) } }

Slide 95

Slide 95

< table

class

" scroll "

< thead

< tr

< th

v-for

" key in columns "

      {{ key }} 
    

</ th

</ tr

</ thead

< tbody

< tr

v-for

" (post , i) in filteredData "

< td

v-for

" entry in columns "

< a

:href

" post . Link "

target

" _blank "

        {{ post[entry] }} 
      

</ a

</ td

</ tr

</ tbody

</ table

Slide 96

Slide 96

Slide 97

Slide 97

this . speakerData . forEach ( function ( index ) {

if (val in endUnit) {

// if we already have this location (stored together as key) let's increment it. 
 // this way of writing it is more performant than functional, 
 if you don’t believe me, let’s arm wrestle.

if (key in endUnit[val]) { endUnit[val][key][ 2 ] += magBase; } else { endUnit[val][key]

[lat , long , magBase]; } } else {

let y

{}; y[key]

[lat , long , magBase]; endUnit[val]

y; } })

Slide 98

Slide 98

THREE.JS Single element < div ref= "container"

</ div

Call on mounted mounted() {

//we have to load the texture when it's mounted and pass it in

let earthmap = THREE.ImageUtils.loadTexture( '/world7.jpg' );

this .initGlobe(earthmap); }

Slide 99

Slide 99

//from const geometry = new THREE.SphereGeometry( 200 , 40 , 30 ); //to const geometry = new THREE.IcosahedronGeometry( 200 , 0 );

Slide 100

Slide 100

VUE.JS && SERVERLESS

Slide 101

Slide 101

THANK YOU! @sarah_edo These slides:
http://bit.ly/2DZfxsw