How To Build A Serverless Webshop

Cover image

Connect Angular and FaunaDB with Netlify Serverless functions

This is a a 6 parts series to build a webshop application with Angular, Netlify Serverless functions in combination with FaunaDB.

Series

  1. Connect Angular and FaunaDB with Netlify Serverless functions
  2. Product list and detail view with Angular
  3. Create crud for products and show products on an Angular page.
  4. Setup Authentication to register and log in (so people can shop)
  5. Create a shopping cart to order products.
  6. Best practices on how to use FaunaDB with Netlify Serverless functions

In this part, we are going to set up a basic Angular application with Angular Material. Next to that, we are creating our Serverless function which is going to be hosted on Netlify. That Serverless function will retrieve data from our database on FaunaDB via the API.

We are exploring this Angular in combination with serverless functions and FaunaDB, because it will make our process of building full stack applications much easier. After this whole series of posts you will become a practical expert in how to use all of these techniques.

Most of the times people would use a NodeJS application, which is in much more need for maintenance than serverless functions.

If you want to learn more about serverless functions, than check my introduction article to it.

Let’s get started and Happy coding! 🚀


1. Install dependencies

Before we start we have to make sure we have a couple of things installed on our computer.

  1. NodeJS & NPM/Yarn: You can download it from the NodeJS website or install it via NVM on Mac or Windows which is more effective when you need to update NodeJS in the future.
  2. Angular CLI: run npm install -g @angular/cli or yarn global add @angular/cli
  3. FaunaDB: run npm install -g fauna-shell or yarn global add fauna-shell

Now that we installed every dependency on our computer, we will set up the project.


2. Create a Github project

First, we are going to create a project on Github so we can store our code safely. Netlify is also using this project to pull the code and build it on their servers. We will work on this project together.

If you want to check for my code, please check the Github repo I will use throughout the whole series.


3. Make an account on Netlify and FaunaDB

Netlify

Go to the Netlify website, log in or register if you don’t have an account yet (you can use Github for it).

FaunaDB

Go to the FaunaDB website, login or register if you don’t have an account yet.


When you are on Netlify click on the “New site from Git”.

Click on the Github button and authorize it with your Github account.

Search for your project and select it.

If you are using a clean project don’t fill in any of the input fields. Only if you’re not working on the master branch off-course.

Now your project is ready to get started!


5. Create Angular Project

Now we are going to create a brand new Angular project. Run ng new project-name in your terminal.

In the branch “add-angular-project” you can see how this looks.

Open this project in your favorite editor/IDE.

I’m going to add Angular Material, but if you want to use something else, please go ahead, this doesn’t affect the tutorial, only a different visual end result 😉

ng add @angular/material

I chose the purple/green theme, added the animations and typography. But pick whatever you like, it has no effect for the rest of the tutorial.

This is how it should look.

The branch can be found on Github.


6. Create functions folder with Netlify config

Now that we have set up the start for our Angular application, it is time to set up our functions folder.

Remember that every JavaScript file that you create here becomes an endpoint. If you have shared or re-usable code don’t put it in the functions folder.

Create a functions folder in the root of your project.

To make sure we can call the serverless function we have to tell the Netlify system where our functions live. We do that in the netlify.toml file.

\[build\]
   functions = “.netlify/functions/”

7. Serverless function example

Netlify builds a tool for running and testing serverless functions on your local computer.

If you use Yarn use this command.

yarn add netlify-lambda

For NPM users use this command.

npm install netlify-lambda — save-dev

In the package.json we add 2 new commands in the scripts to make it easier to call. See the fn:serve and fn:build commands.

"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e",
"fn:serve": "netlify-lambda serve functions",
"fn:build": "netlify-lambda build functions"
},
view raw package.json hosted with ❤ by GitHub

Now create the first serverless function hello.js in the functions folder.

export async function handler(event, context) {
return {
statusCode: 200, // response status code
body: 'Hello, World', // response body
}
}
view raw hello.js hosted with ❤ by GitHub

Run yarn fn:serve or npm run fn:serve to test the function locally. For making a build run yarn fn:build or npm run fn:build.

If you want to have support for TypeScript, check how to set it up on the Netlify-lambda repo.

Now push this to GitHub. The Netlify build will start and make your function visible on the “functions” page.

In this branch, you can check this function on Github.


8. Create Fauna DB collection

Create an account on FaunaDB if you haven’t already. Otherwise go to the FaunaDB dashboard, login and create a new database.

Give your database the name that you want. For the purpose of building a webshop, I select the “Pre-populate with demo data” option.

This option fills your database with a couple of collections, indexes, and data. I think this is super useful when discovering the FaunaDB service!


9. Create a Security Key

Click on “security” on the sidebar and create a new security key. Initialy there are two Roles to choose from, Admin and Server. Choose the Server role, we do not need Admin access. You can create your own fine-grained roles later if you want to restrict access further.

After saving you will see a key, copy this key and keep it private! Go to the settings of Netlify.

Find the “Environment variables” section and create your environment variable. I use the secret key: “FAUNADB_SERVER_SECRET” and paste the security key from FaunaDB in the value input.


10. Create a Config File

I think it’s a good idea to create a lib folder where we can put all the logic for our functions.

We need to install the faunadb npm package to access the FaunaDB API. To have access to the FAUNADB_SERVER_SECRET we need to install dotenv.

For NPM:

npm install faunadb dotenv

For Yarn:

yarn add faunadb dotenv

We create config.js outside the functions folder, where we can add header information, the Fauna DB server secret, and more.

import faunadb from 'faunadb'
require('dotenv').config()
const headers = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE',
}
const client = new faunadb.Client({
secret: process.env.FAUNADB_SERVER_SECRET,
})
export { client, headers }
view raw config.js hosted with ❤ by GitHub

To have a local environment variable FAUNADB_SERVER_SECRET we create a .env file in the main directory of the project.

FAUNADB\_SERVER\_SECRET=type-code-here

11. Get product data from FaunaDB

In this function, I want to retrieve the data from the products. In that folder we create product-service.js.

import faunadb from 'faunadb'
const q = faunadb.query
export class ProductService {
constructor(data) {
this.client = data.client
}
async getProducts() {
return new Promise((resolve, reject) => {
this.client
.query(q.Paginate(q.Match(q.Ref('indexes/all_products'))))
.then((response) => {
const productRefs = response.data
const getAllProductDataQuery = q.Map(productRefs, q.Lambda(['ref'], q.Get(q.Var('ref'))))
this.client.query(getAllProductDataQuery).then((ret) => {
resolve(ret)
})
})
.catch((error) => {
console.log('error', error)
reject(error)
})
})
}
}
view raw product-service.js hosted with ❤ by GitHub

The ProductService class will be used in our products function.

Create a products.js in the functions folder and import the faunadb package and create a client where we use the environment variable.

import { ProductService } from '../lib/product-service.js'
import { client, headers } from '../lib/config.js'
const service = new ProductService({ client })
exports.handler = async (event, context) => {
console.log('Function `products` invoked')
if (event.httpMethod !== 'GET') {
return { statusCode: 405, headers, body: 'Method Not Allowed' }
}
try {
const products = await service.getProducts()
return {
statusCode: 200,
headers,
body: JSON.stringify(products),
}
} catch (error) {
console.log('error', error)
return {
statusCode: 400,
headers,
body: JSON.stringify(error),
}
}
}
view raw products.js hosted with ❤ by GitHub

To test if everything works as intended, we have to run yarn fn:serve or npm run fn:serve. When the build is finished successfully check this url in the browser: http://localhost:9000/.netlify/functions/products. The data you should get back looks similar as the example below.

[
{
"ref": {
"@ref": {
"id": "266790280843231752",
"collection": {
"@ref": {
"id": "products",
"collection": {
"@ref": {
"id": "collections"
}
}
}
}
}
},
"ts": 1590689888787000,
"data": {
"name": "Cup",
"description": "Translucent 9 Oz",
"price": 6.9,
"quantity": 100,
"storehouse": {
"@ref": {
"id": "266790280839037448",
"collection": {
"@ref": {
"id": "storehouses",
"collection": {
"@ref": {
"id": "collections"
}
}
}
}
}
},
"backorderLimit": 5,
"backordered": false
}
},
{
"ref": {
"@ref": {
"id": "266790280851620360",
"collection": {
"@ref": {
"id": "products",
"collection": {
"@ref": {
"id": "collections"
}
}
}
}
}
},
"ts": 1590689888787000,
"data": {
"name": "Beef Cheek",
"description": "Fresh",
"price": 5.28,
"quantity": 100,
"storehouse": {
"@ref": {
"id": "266790280839038472",
"collection": {
"@ref": {
"id": "storehouses",
"collection": {
"@ref": {
"id": "collections"
}
}
}
}
}
},
"backorderLimit": 10,
"backordered": false
}
},
{
"ref": {
"@ref": {
"id": "266790280852668936",
"collection": {
"@ref": {
"id": "products",
"collection": {
"@ref": {
"id": "collections"
}
}
}
}
}
},
"ts": 1590689888787000,
"data": {
"name": "Pizza",
"description": "Frozen Cheese",
"price": 4.07,
"quantity": 100,
"storehouse": {
"@ref": {
"id": "266790280829600264",
"collection": {
"@ref": {
"id": "storehouses",
"collection": {
"@ref": {
"id": "collections"
}
}
}
}
}
},
"backorderLimit": 15,
"backordered": false
}
}
]

Push the changes to Github, wait until the build finished and test the URL from Netlify to see if everything works as expected.

In this branch, you can check the product function on Github. You can also check the preview URL from my application.


12. Call serverless function from Angular and show results.

Now that we have seen that the serverless function gives back the data we would expect we can retrieve the data from our Angular application.

We need to make sure that our app.module.ts has the HttpsClientModule imported.

import { BrowserModule } from '@angular/platform-browser'
import { NgModule } from '@angular/core'
import { AppRoutingModule } from './app-routing.module'
import { AppComponent } from './app.component'
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
import { HttpClientModule } from '@angular/common/http'
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, HttpClientModule, AppRoutingModule, BrowserAnimationsModule],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
view raw app.module.ts hosted with ❤ by GitHub

And in the app.component.ts we need to use the ngOnInit method to get the data when our component is finished with rendering. We need to import the HttpClientand use it in the ngOnInit method to call our local serverless function.

import { Component, OnInit } from '@angular/core'
import { HttpClient } from '@angular/common/http'
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent implements OnInit {
title = 'ng-faunadb-netlify-serverless-functions'
constructor(private http: HttpClient) {}
ngOnInit() {
this.http.get('http://localhost:9000/.netlify/functions/products').subscribe((response) => {
console.log('response: ', response)
})
}
}
view raw app.component.ts hosted with ❤ by GitHub

Test in the browser if you can see the data from the products.

Conclusion

Now we have learned how to make a serverless function, get data from the FaunaDB and retrieve it in the Angular application.

Next time we go a step further with Angular, FaunaDB and Netlify’s serverless function. If you can’t wait for the next tutorial, please experiment a bit more with what we already have build.

Happy Coding 🚀

I’ve gathered a couple of aspiring developers around the world on a Discord server, feel free if you like to join in.


Read more

  1. TypeScript’s New Top-Level Await
  2. 4 Steps to Get Started With Serverless Functions on Netlify
  3. Native Lazy Loading in the Browser