SERVERLESS FUNCTIONS AND VUE.JS

SUCCESS!

NOT FAIL…

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

SARAH DRASNER @SARAH_EDO

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

LINK TO CODEPEN

LINK TO CODEPEN

SERVERLESS ALL THE THINGS? NO, NOT REALLY.

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

Consumption Plan Free account signup

VUE.JS, STRIPE, AND SERVERLESS FIRST USE CASE

SAMPLE VUE SHOP VISIT THE SITE

THE APPLICATION VISIT THE REPO

UNDER API

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

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

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' );

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 }); }) ...

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(); } };

SUCCESS! POST ON CSS-TRICKS

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

< 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

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

computed: { ... total() {

return

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

VUE STRIPE CHECKOUT EASIEST WAY REPO

STRIPE ELEMENTS WE’LL USE:

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

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

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)) } } }

WHAT WE’LL NEED: data() {

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

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

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

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' } } ) ...

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); }); });

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

WHILE THE FUNCTION WORKS ITS MAGIC ✨ LINK TO CODEPEN

IN THE CASE OF FAILURE

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 = '' ; }

IN THE CASE OF SUCCESS

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

< 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

< 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

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

Ice Cream %

Puppy &

Things we like ❤

MAKE THE NEW FUNCTION

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

{

"bindings" : [ {

"authLevel" : " function " ,

"type" : " httpTrigger " ,

"direction" : " in " ,

"name" : " req "

}, 
{ 
  

"type" : " http " ,

"direction" : " out " ,

"name" : " res "

} 

],

"disabled" : false

}

{

"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

}

{

… {

"type" : " documentDB " ,

"name" : " inputDocument " ,

"databaseName" : " CocktailFinder " ,

"collectionName" : " Cocktails " ,

"partitionKey" : " /id " ,

"connection" : “ sdrasapidemo_DOCUMENTDB " ,

"direction" : " in "

} 

], … }

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 (); };

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 (); };

https://bit.ly/2KB13pA

SUCCESS!

export

default {

…

created () {

this.axios

. get (

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

  ) 
  

. then ( response

!" {

this.info

= response .data.body

  }) 

} }

pages/Item.vue

<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

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

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

[
{

" 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

MAKE THE FUNCTION

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

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;

[
{

" 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

[
{

" 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

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 ; }) } }

< 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

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; } })

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); }

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

VUE.JS && SERVERLESS

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