A presentation at Artifact in in Austin, TX, USA by Divya
JAMstackin’ Divya Sasidharan
Divya Sasidharan @shortdiv Netlify
<> JAM APIs Markup Javascript stack
A modern architecture — Create fast and secure sites and dynamic apps with JavaScript, APIs, and prerendered Markup served without web servers. JAM stack
served without web servers. Static
Dynamic
Markup Server
DNS Browser Load Balancer CDN Cloud Storage Caching … Services Web App Server Database Server
DNS Browser CDN … Services
Immutable Deploys Build Automation Event Triggers Serverless
Immutable Deploys Build Automation Event Triggers Serverless
ild Bu a ner Ge te Deploy
html css js md
This works? Jen told me not to swear but f**k I have no idea why this works Why is this not working Work in Progress Initial
Atomic Deploys
A B
A A
Immutable Deploys Build Automation Event Triggers Serverless
html css js md
md
_ html css js
Immutable Deploys Build Automation Event Triggers Serverless
md html css js
{…} Process Payment Success webhooks {…} Failure Cancel Order
Web Hooks
Git Activity Split Test Events Build/Deploy Events Form Submissions Content Updates Auth Events
Immutable Deploys Build Automation Event Triggers Serverless
DNS Browser CDN … Services
Frontend Framework … Server CDN Browser Static Site … Full Stack Application
Backend Application Frontend Framework … … CDN … Static Site
Frontend Framework … CDN … Static Site
Frontend Framework … CDN …
Functions as a Service
Good News Everyone!
emo Time! Demo Time! Demo Tim emo Time! Demo Time! Demo Tim emo Time! Demo Time! Demo Tim emo Time! Demo Time! Demo Tim emo Time! Demo Time! Demo Tim emo Time! Demo Time! Demo Tim
Forms Functions Authentication Event triggers ✨
1 <form 2 name=“delivery-request“ 3 method=“POST” 4 netlify-honeypot=“bot-field” 5 data-netlify=“true” 6 > 7 <p class=”hidden”> 8 <label> 9 Don’t fill this out if you’re human: 10 <input name=”bot-field” /> 11 </label> 12 </p> 13 <p> 14 <label>Contents: 15 <input type=”text” name=”contents” /> 16 </label> 17 </p> 18 <fieldset> 19 <label> 20 <input 21 name=”chosenCrew” 22 type=”checkbox” 23 /> 24 <span>Leela</span> 25 </label> 26 <label> 27 <input 28 name=”chosenCrew”
1 <template> 2 <div class=”hello”> 3 <form 4 name=”delivery-request” 5 method=”post” 6 data-netlify=”true” 7 data-netlify-honeypot=”bot-field” 8 @submit.prevent=”handleSubmit” 9 > 10 <header> 11 <h2>Planet Express Delivery Request</h2> 12 </header> 13 <input type=”hidden” name=”form-name” value=”delivery-request” / 14 <input type=”hidden” id=”bot” name=”bot” /> 15 <div> 16 <label> 17 <span>Contents</span> 18 <input 19 id=”contents” 20 name=”contents” 21 type=”text” 22 v-model=”form.contents” 23 /> 24 </label> 25 … 26 </div> 27 <button type=”submit” class=”submit-button”>Request Job</button> 28 </form>
}; 46 }, 47 methods: { 48 encode(data) { 49 return Object.keys(data) 50 .map( 51 key => `${encodeURIComponent(key)}=${encodeURIComponent(data 52 ) 53 .join(“&”); 54 }, 55 handleSubmit() { 56 const axiosConfig = { 57 header: { “Content-Type”: “application/x-www-form-urlencoded” 58 }; 59 const payload = { 60 “form-name”: “delivery-request”, 61 …this.form 62 }; 63 axios 64 .post(“/”, this.encode(payload), axiosConfig) 65 .then(() => { 66 this.$router.push(“thanks”); 67 }) 68 .catch(() => { 69 this.$router.push(“404”); 70 }); 71 } 72 } 73 }; 74 </script>
Forms Functions Authentication Event triggers ✨
exports.handler = function(event, context, callback) { console.log(event) callback(null, { statusCode: 200, body: JSON.stringify({ msg: “Hello, World!” }) }) } functions/hello.js
exports.handler = function(event, context, callback) { console.log(event) callback(null, { statusCode: 200, body: JSON.stringify({ msg: “Hello, World!” }) }) } functions/submission-created.js
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 /* Triggered when a form submission is posted to your site. */ const faunadb = require(“faunadb”); const q = faunadb.query; const client = new faunadb.Client({ secret: process.env.FAUNA_SECRET } exports.handler = (event, context, callback) => { const body = JSON.parse(event.body).payload; const { contents, chosenCrew, recipient, destination } = body.data; client .query( q.Create(q.Class(“deliveries”), { data: { Contents: contents, Crew: chosenCrew.split(“,”), Recipient: recipient, Destination: destination, status: “pending” } }) ) .then(ret => { console.log(ret);
6 7 exports.handler = (event, context, callback) => { 8 const body = JSON.parse(event.body).payload; 9 const { 10 contents, 11 chosenCrew, 12 recipient, 13 destination 14 } = body.data; 15 client 16 .query( 17 q.Create(q.Class(“deliveries”), { 18 data: { 19 Contents: contents, 20 Crew: chosenCrew.split(“,”), 21 Recipient: recipient, 22 Destination: destination, 23 status: “pending” 24 } 25 }) 26 ) 27 .then(ret => { 28 console.log(ret); 29 return callback(null, { 30 statusCode: 200, 31 body: JSON.stringify(body) 32 }); 33 }) 34 .catch(err => { 35 return callback(null, {
6 7 exports.handler = (event, context, callback) => { 8 const body = JSON.parse(event.body).payload; 9 const { 10 contents, 11 chosenCrew, 12 recipient, 13 destination 14 } = body.data; 15 client 16 .query( 17 q.Create(q.Class(“deliveries”), { 18 data: { 19 Contents: contents, 20 Crew: chosenCrew.split(“,”), 21 Recipient: recipient, 22 Destination: destination, 23 status: “pending” 24 } 25 }) 26 ) 27 .then(ret => { 28 console.log(ret); 29 return callback(null, { 30 statusCode: 200, 31 body: JSON.stringify(body) 32 }); 33 }) 34 .catch(err => { 35 return callback(null, {
6 7 exports.handler = (event, context, callback) => { 8 const body = JSON.parse(event.body).payload; 9 const { 10 contents, 11 chosenCrew, 12 recipient, 13 destination 14 } = body.data; 15 client 16 .query( 17 q.Create(q.Class(“deliveries”), { 18 data: { 19 Contents: contents, 20 Crew: chosenCrew.split(“,”), 21 Recipient: recipient, 22 Destination: destination, 23 status: “pending” 24 } 25 }) 26 ) 27 .then(ret => { 28 console.log(ret); 29 return callback(null, { 30 statusCode: 200, 31 body: JSON.stringify(body) 32 }); 33 }) 34 .catch(err => { 35 return callback(null, {
6 7 exports.handler = (event, context, callback) => { 8 const body = JSON.parse(event.body).payload; 9 const { 10 contents, 11 chosenCrew, 12 recipient, 13 destination 14 } = body.data; 15 client 16 .query( 17 q.Create(q.Class(“deliveries”), { 18 data: { 19 Contents: contents, 20 Crew: chosenCrew.split(“,”), 21 Recipient: recipient, 22 Destination: destination, 23 status: “pending” 24 } 25 }) 26 ) 27 .then(ret => { 28 console.log(ret); 29 return callback(null, { 30 statusCode: 200, 31 body: JSON.stringify(body) 32 }); 33 }) 34 .catch(err => { 35 return callback(null, {
Forms Functions Authentication Event triggers ✨
Netlify Identity GoTrue
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 import Vue from ‘vue’ import Vuex from ‘vuex’ import GoTrue from “gotrue-js”; Vue.use(Vuex) const auth = new GoTrue({ APIUrl: “https://planet-express-deliveries.netlify.com/.netlify/iden audience: “”, setCookie: false }); export default new Vuex.Store({ state: { currentUser: getSavedState(“auth.currentUser”), loggedIn: false, deliveries: [] }, mutations: { SET_CURRENT_USER(state, value) { state.currentUser = value; } }, getters: { loggedIn(state) { return !!state.currentUser; } },
35 36 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 63 64 }, attemptLogin({ commit, dispatch }, credentials) { return new Promise((resolve, reject) => { auth .login(credentials.email, credentials.password) .then(response => { resolve(response); commit(“SET_CURRENT_USER”, response); }) .catch(error => { reject(error.json); }); }); }, attemptLogout({ commit }) { return new Promise((resolve, reject) => { const user = auth.currentUser(); user .logout() .then(response => { console.log(response); resolve(response); commit(“SET_CURRENT_USER”, null); }) .catch(error => { reject(error); console.log(“Could not log out”, error); }); }); },
35 36 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 63 64 }, attemptLogin({ commit, dispatch }, credentials) { return new Promise((resolve, reject) => { auth .login(credentials.email, credentials.password) .then(response => { resolve(response); commit(“SET_CURRENT_USER”, response); }) .catch(error => { reject(error.json); }); }); }, attemptLogout({ commit }) { return new Promise((resolve, reject) => { const user = auth.currentUser(); user .logout() .then(response => { console.log(response); resolve(response); commit(“SET_CURRENT_USER”, null); }) .catch(error => { reject(error); console.log(“Could not log out”, error); }); }); },
Forms Functions Authentication Event triggers ✨
The Road to JAMstack
Get to the CDN Design for immutable deploys Automate all the things Serverless all the things
Dynamic Static
Thanks! @shortdiv
Static sites have made a comeback as of late. This “return to simplicity’ is unsurprising given the current complexity of websites and applications that make maintenance quite the nightmare. Regardless, static sites today are a far cry from their older counterparts. Static sites circa early 2000s consisted of static markup with some CSS thrown in for flavor. Today, static sites are much more dynamic and are a healthy mix of JavaScript, APIs and Markup, aka JAM. They are capable of more complex behavior like processing payments and making authentication requests that are traditionally associated with backend server logic. In this talk, let’s dive into the wonderful world of the JAMstack and how we can harness it to make faster, more secure websites and applications.