A presentation at Frontend Developer Love in in Amsterdam, Netherlands by Sarah Drasner
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 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 () {
new
RegExp ( this . filteredText , ' i ' )
return
this . speakerData . filter ( el
=> {
if (el[x] !==
undefined ) { return el[x] . match (filter) }
else
return
true ; }) } }
< table
" scroll "
< thead
< tr
< th
" key in columns "
{{ key }}
</ th
</ tr
</ thead
< tbody
< tr
" (post , i) in filteredData "
< td
" entry in columns "
< a
" post . Link "
" _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.
[lat , long , magBase]; } } else {
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
Serverless is the most clickbaity title for an actually interesting thing. Despite the name, Serverless does not mean you’re not using a server, rather, the promise of Serverless is to no longer have to babysit a server. Scaling is done for you, you’re billed only for what you use. In this session, we’ll cover some key use cases for these functions within a Vue.js application: we’ll accept payments with stripe, we’ll gather geolocation data from Google Maps, and more! We’ll make it all work with Vue and Nuxt seamlessly, simplifying how to leverage this paradigm to be a workhorse for your application.
The following code examples from the presentation can be tried out live.