Skip to content

The-Commit-Company/frappe-js-sdk

Repository files navigation

frappe-js-sdk

TypeScript/JavaScript library for a Frappe Framework backend.


Features

The library currently supports the following features:

  • 🔐 Authentication - login with username and password (cookie based) + token based authentication
  • 🗄 Database - Get document, get list of documents, get count, create, update and delete documents
  • 📄 File upload
  • 🤙🏻 API calls

We plan to add the following features in the future:

  • Support for common functions like exists in the database.

The library uses Axios under the hood to make API calls to your Frappe backend.

Maintainers

Maintainer GitHub Social
Nikhil Kothari nikkothari22 @nik_kothari22
Janhvi Patil janhvipatil @janhvipatil_
Sumit Jain sumitjain236 LinkedIn

Installation

npm install frappe-js-sdk

or

yarn add frappe-js-sdk

Initialising the library

To get started, initialise the library:

import { FrappeApp } from 'frappe-js-sdk';
//Add your Frappe backend's URL
const frappe = new FrappeApp('https://test.frappe.cloud');

In case you want to use the library with token based authentication (OAuth bearer tokens or API key/secret pairs), you can initialise the library like this:

import { FrappeApp } from "frappe-js-sdk";

const frappe = new FrappeApp("https://test.frappe.cloud", {
    useToken: true,
    // Pass a custom function that returns the token as a string - this could be fetched from LocalStorage or auth providers like Firebase, Auth0 etc.
    token: getTokenFromLocalStorage(),
    // This can be "Bearer" or "token"
    type: "Bearer"
})

Authentication

Initialise the auth library

const auth = frappe.auth()

Login a user:

This makes an API call to the /api/method/login endpoint.

auth
  .loginWithUsernamePassword({ username: 'admin', password: 'my-password' })
  .then((response) => console.log('Logged in'))
  .catch((error) => console.error(error));

Get currently logged in user:

This makes an API call to the /api/method/frappe.auth.get_logged_user endpoint.

auth
  .getLoggedInUser()
  .then((user) => console.log(`User ${user} is logged in.`))
  .catch((error) => console.error(error));

Logout:

This makes an API call to the /api/method/logout endpoint.

auth
  .logout()
  .then(() => console.log('Logged out.'))
  .catch((error) => console.error(error));

Forget Password

This makes an API sends a password reset link to the specified email address.

auth
  .forgetPassword('[email protected]')
  .then(() => console.log('Password Reset Email Sent!'))
  .catch(() => console.error("We couldn't find your account."));

Database

Initialise the database library

const db = frappe.db();

Fetch document using document name

db.getDoc('DocType', 'My DocType Name')
  .then((doc) => console.log(doc))
  .catch((error) => console.error(error));

Fetch list of documents

db.getDocList('DocType')
  .then((docs) => console.log(docs))
  .catch((error) => console.error(error));

Optionally, a second argument can be provided to filter, sort, limit and paginate results.

db.getDocList('DocType', {
  /** Fields to be fetched */
  fields: ['name', 'creation'],
  /** Filters to be applied - SQL AND operation */
  filters: [['creation', '>', '2021-10-09']],
  /** Filters to be applied - SQL OR operation */
  orFilters: [],
  /** Fetch from nth document in filtered and sorted list. Used for pagination  */
  limit_start: 5,
  /** Number of documents to be fetched. Default is 20  */
  limit: 10,
  /** Sort results by field and order  */
  orderBy: {
    field: 'creation',
    order: 'desc',
  },
  /** Group the results by particular field */
  groupBy: 'name',
  /** Fetch documents as a dictionary */
  asDict: false,
})
  .then((docs) => console.log(docs))
  .catch((error) => console.error(error));

Type declarations are available for the second argument in the source code.

Fetch number of documents with filters

const filters = [['creation', '>', '2021-10-09']];
const useCache = true; /** Default is false - Optional **/
const debug = false; /** Default is false - Optional **/

db.getCount('DocType', filters, cache, debug)
  .then((count) => console.log(count))
  .catch((error) => console.error(error));

Create a document

To create a new document, pass the name of the DocType and the fields to createDoc.

db.createDoc('My Custom DocType', {
  name: 'Test',
  test_field: 'This is a test field',
})
  .then((doc) => console.log(doc))
  .catch((error) => console.error(error));

Update a document

To update an existing document, pass the name of the DocType, name of the document and the fields to be updated to updateDoc.

db.updateDoc('My Custom DocType', 'Test', {
  test_field: 'This is an updated test field.',
})
  .then((doc) => console.log(doc))
  .catch((error) => console.error(error));

Delete a document

To create a new document, pass the name of the DocType and the name of the document to be deleted to deleteDoc.

db.deleteDoc('My Custom DocType', 'Test')
  .then((response) => console.log(response.message)) // Message will be "ok"
  .catch((error) => console.error(error));

Usage with Typescript

The library supports Typescript out of the box. For example, to enforce type on the updateDoc method:

interface TestDoc {
  test_field: string;
}
db.updateDoc<TestDoc>('My Custom DocType', 'Test', {
  test_field: 'This is an updated test field.',
});

The library also has an inbuilt type FrappeDoc which adds the following fields to your type declarations when you use it with the database methods:

export type FrappeDoc<T> = T & {
  /** User who created the document */
  owner: string;
  /** Date and time when the document was created - ISO format */
  creation: string;
  /** Date and time when the document was last modified - ISO format */
  modified: string;
  /** User who last modified the document */
  modified_by: string;
  idx: number;
  /** 0 - Saved, 1 - Submitted, 2 - Cancelled */
  docstatus: 0 | 1 | 2;
  parent?: any;
  parentfield?: any;
  parenttype?: any;
  /** The primary key of the DocType table */
  name: string;
};

All document responses are returned as an intersection of FrappeDoc and the specified type.

API Calls

Initialise the call library

const call = frappe.call();

Make sure all endpoints are whitelisted (@frappe.whitelist()) in your backend

GET request

Make a GET request to your endpoint with parameters.

const searchParams = {
  doctype: 'Currency',
  txt: 'IN',
};
call
  .get('frappe.desk.search_link', searchParams)
  .then((result) => console.log(result))
  .catch((error) => console.error(error));

POST request

Make a POST request to your endpoint with parameters.

const updatedFields = {
  doctype: 'User',
  name: 'Administrator',
  fieldname: 'interest',
  value: 'Frappe Framework, ERPNext',
};
call
  .post('frappe.client.set_value', updatedFields)
  .then((result) => console.log(result))
  .catch((error) => console.error(error));

PUT request

Make a PUT request to your endpoint with parameters.

const updatedFields = {
  doctype: 'User',
  name: 'Administrator',
  fieldname: 'interest',
  value: 'Frappe Framework, ERPNext',
};
call
  .put('frappe.client.set_value', updatedFields)
  .then((result) => console.log(result))
  .catch((error) => console.error(error));

DELETE request

Make a DELETE request to your endpoint with parameters.

const documentToBeDeleted = {
  doctype: 'Tag',
  name: 'Random Tag',
};
call
  .put('frappe.client.delete', documentToBeDeleted)
  .then((result) => console.log(result))
  .catch((error) => console.error(error));

File Uploads

Initialise the file library

const file = frappe.file();

Upload a file with on progress callback

const myFile; //Your File object

const fileArgs = {
  /** If the file access is private then set to TRUE (optional) */
  "isPrivate": true,
  /** Folder the file exists in (optional) */
  "folder": "Home",
  /** File URL (optional) */
  "file_url": "",
  /** Doctype associated with the file (optional) */
  "doctype": "User",
  /** Docname associated with the file (mandatory if doctype is present) */
  "docname": "Administrator",
  /** Field in the document **/
  "fieldname": "image"
}

file.uploadFile(
            myFile,
            fileArgs,
            /** Progress Indicator callback function **/
            (completedBytes, totalBytes) => console.log(Math.round((completedBytes / totalBytes) * 100), " completed")
        )
        .then(() => console.log("File Upload complete"))
        .catch(e => console.error(e))

License

See LICENSE.