Introduction
Frappe Framework has a built-in "automagical" REST API. You can use this API to talk to your Frappe backend in order to perform CRUD operations and more. But calling the API endpoints manually and handling the response can be a tedious process. To solve this problem, we had the frappe-client
library, a Python wrapper for the REST API, which made it very easy to use the REST API in Python applications. But not everything is written in Python, isn't it?
JavaScript is one of the most popular programming languages and many great libraries and frameworks have been built on top of it. So, it is time for the Frappe JS SDK! frappe-js-sdk
is a JavaScript package, developed by Nikhil Kothari and team, that makes it super easy to integrate a Frappe backend in your JavaScript/TypeScript applications. Yours truly is also a contributor to this library 😆
Prerequisites
This tutorial assumes you know the basics of Frappe (sites, DocTypes etc.) and JavaScript/NodeJS.
Why?
There can be a variety of reasons why you might want to access/integrate your Frappe backend in NodeJS and/or server-less functions:
- To use your Frappe backend as a data store
- To integrate your Frappe instance into an existing infrastructure
- To make Frappe part of your microservices architecture
- To aggregate data from various Frappe Instances (say for example data processing requirements etc.)
Introduction to Frappe JS SDK
frappe-js-sdk
is a JavaScript/TypeScript library that makes it very easy to interact with the Frappe REST API in JavaScript applications. This includes front-end (browser-based) web apps as well as NodeJS based server-side services. You can choose to authenticate using username/password or API tokens (as we will see in this tutorial).
As a bonus, the library is fully written in TypeScript, so you get all the typing and auto-completion magic in your code editors. Enough with the introduction, let's build something now!
Create A Frappe Site
Feel free to skip this step if you already have a live Frappe site. You can also setup and use a site on your local machine.
I am going to use a site hosted on Frappe Cloud. You can easily create a new site by navigating to the Sites tab and clicking on the + New
button.
The Expense Scenario
DocTypes in Frappe are basically analogous to database models in other web frameworks like Django, but much more powerful. Once we create a DocType, we can immediately use the REST API to perform CRUD operations on that DocType.
For the purpose of this tutorial, imagine we want to store expenses in our Frappe site. We will create a new DocType to track expenses.
Creating the Expense
DocType
- Navigate to the DocType list page. You can either use the shortcut link or utilize the awesome bar to search for the page:
- Click on the
+ Add DocType
button:
- Give the DocType a name and select a module for the DocType. I have created a new Module Def named "Expense Tracker" and selected it.
Add in two fields to this DocType:
- For -
Data
- What the expense for? - Amount -
Currency
- How much was it?
I have also enabled the 'In List View' options for both this fields, so they will show up in the Expense
DocType's list view.
Done.
Obtaining API Keys
We will need API keys in order to access the REST API (with or without the JavaScript SDK). In order to get the API keys for a particular user in Frappe, we can open that user's document (User
DocType) and navigate to the Settings
tab:
Click on the Generate Keys
button in the "API Access" section. A pop-up will appear with the API Secret, copy it somewhere safe as it can be viewed only once:
Also, note down the API Key:
The authentication token will be formed using both the API Key and the API Secret.
Using The JS SDK In NodeJS Project
Let's create a new directory (named playground
in this case) in our system and initialise an empty node project:
$ mkdir playground && cd playground
$ npm init --yes # generates package.json
Now, we can install the frappe-js-sdk
library using npm:
$ npm install frappe-js-sdk
Let's also create a JavaScript file where we will write our code:
$ touch index.js
Now, open up the playground/index.js
in a code editor of your choice, mine is VSCode. We will start by importing the FrappeApp
class and creating a few constants:
import { FrappeApp } from "frappe-js-sdk";
const SITE_URL = "https://hussain.codes"; // Replace with your site's URL
const API_SECRET = "<API-SECRET>";
const API_KEY = "<API-KEY>";
For the sake of brevity, I have stored the API credentials in the code itself, but DO NOT do this in production settings. Store them in environment variables or a secret store.
Initialise And Use The Library
As mentioned earlier, the SDK also supports username/password based auth when run in browser-based environments. But we are going to use token based auth, hence, we set useToken
to true
and provide a function as the token
argument. The API token is formed by adding a :
in between the key and the secret as shown below:
const frappe = new FrappeApp(SITE_URL, {
useToken: true,
token: () => `${API_KEY}:${API_SECRET}`,
type: "token", // use "bearer" in case of oauth token
});
const db = frappe.db(); // Initialise the `db` class
Expense CRUD
Creating New Documents
Creating a new expense record is as easy as:
await db.createDoc("Expense", { for: "Coffee", amount: 120 });
Let's run our script using node now:
$ node index.js
After the script finishes executing, open up the Expense
List on your Frappe site:
TADA 🎉!
Reading A List Of Documents
We can use the getDocList
method to get the list of documents for a given doctype:
const expenses = await db.getDocList("Expense", {
fields: ["for", "amount", "creation"]
})
console.log(expenses)
When we run the script, we will get the below output:
You can provide a variety of different options for getting the list via the second argument to this method. In the above case, I am specifying the fields
I want to fetch. You can also provide more options to perform filter, sort, limit etc.
Updating An Existing Document
You must know the name of the document in order to update or delete it. If we want to update the fields of an existing document, we can use the updateDoc
method:
await db.updateDoc("Expense", "aa49085aa3", { amount: 200 }); // Update the value of `amount` to 200
Check your expense list again, the update should get reflected.
Deleting A Document
You might have guessed this one by now, we will use the deleteDoc
method to delete a document from our database:
await db.deleteDoc("Expense", "aa49085aa3");
Integrating Frappe In Serverless Functions
As we have seen in the above sections, we can install and use the frappe-js-sdk
in a NodeJS environment. This includes server-less (or lambda) functions that support the NodeJS runtime. You can read more about the benefits of server-less functions here.
Server-less functions are, in general, stateless. So they don't store any data and rely on an external data store. Let's use frappe-js-sdk
to connect our Frappe instance to a server-less function. I am going to use Firebase Cloud Functions in this tutorial but integrating with other FaaS (Function as a Service) like AWS Lambda, Netlify Functions, etc. should be identical.
Firebase Cloud Functions
Firebase Cloud Functions run in a NodeJS environment and have a generous free-tier. You will need a (free) Firebase account in order to follow along.
Install & Setup Firebase CLI
I will globally install the Firebase CLI using npm
:
$ npm install -g firebase-tools
Let's authenticate with our Firebase account:
$ firebase login
Create A New Project
We will now create an empty directory and cd
into it:
$ mkdir frappe_firebase && cd frappe_firebase
We can initialize a new Firebase project inside this directory, with only functions
feature using the below command:
$ firebase init functions
Running the above command will walk you through a prompt where you will be able to select your Firebase Project (to which the functions will be deployed) and preferred language (JS/TS). Once the wizard is complete, let's open up the directory in VSCode:
Installing frappe-js-sdk
Before we can write any logic, let's install the frappe-js-sdk
using npm
in our functions directory:
$ cd functions
$ npm install frappe-js-sdk
Writing The Function
Suppose we want to write a function that returns the sum of all our expenses. We will fetch list of all expenses from our Frappe backend, perform the sum and return the result of the calculation as the response of the function.
We will start by deleting the boilerplate code in index.js
and importing the required stuff:
const functions = require("firebase-functions");
const { FrappeApp } = require("frappe-js-sdk");
const { defineString } = require("firebase-functions/params");
We can use the defineString
function to provide parameters to the function at deploy time. We will use this function to provide the API credentials, so we don't have to store it as plain text in our code:
const SITE_URL = "https://hussain.codes";
const API_KEY = defineString("FRAPPE_API_KEY");
const API_SECRET = defineString("FRAPPE_API_SECRET");
function getToken() {
return `${API_KEY.value()}:${API_SECRET.value()}`;
}
I have also written a function that returns the auth token by extracting the values (using .value()
method) of API key and API secret at runtime. We are ready to write the function now:
exports.totalExpenses = functions.https.onRequest(async (request, response) => {
const frappe = new FrappeApp(SITE_URL, {
useToken: true,
token: getToken, // pass the function itself
type: "token",
});
const db = frappe.db(); // init the `db` class
// Fetch all expenses (only the "amount" field)
const expenses = await db.getDocList("Expense", {
fields: ["amount"],
limit: 9999 // default is 20
});
// Perform the sum
let sum = 0;
for (let expense of expenses) {
sum += expense.amount;
}
// Return a JSON object as reponse
response.send({totalExpense: sum});
});
In the above code, we are first initializing the FrappeApp
instance using the site URL and API credentials (token). Then, we fetch the list of expenses and sum all the amounts. In the end, we are returning the sum.
Testing The Function Locally
We can test our newly created function (totalExpense
) locally by running the Firebase emulators:
$ firebase emulators:start
Running the above command will start emulating the function locally and show us the endpoint:
If you are running the emulators for the first time, it will prompt you to provide the value for the
API_KEY
andAPI_SECRET
variables.
Opening the above endpoint in our browser should show the desired result:
Yay! Our Firebase function is now successfully integrated to our Frappe backend!
Resources
- Frappe JS SDK Docs: GitHub Repo (Leave a star!)
- Source Code for Firebase Function example: GitHub Repo
Conclusion
That's it for today. I hope you find this tutorial helpful. Let me know if you want to see more content on frappe-js-sdk
, for example, integrating in modern front-end frameworks. Will be back! Till then, Ciao!