The FreshBooks NodeJS SDK is a collection of single-purpose packages designed to easily build FreshBooks apps. Each package delivers part of the FreshBooks API, so that you can choose the packages that fit your needs.
Package | What it's for |
---|---|
@freshbooks/api |
Get/set data from FreshBooks using the REST API. |
@freshbooks/events |
Register/listen for incoming events via webhooks. |
@freshbooks/app |
Pre-configured ExpressJS app. Includes authentication via PassportJS. |
Use your favorite package manager to install any of the packages and save to your package.json
:
$ npm install @freshbooks/api @freshbooks/events @freshbooks/app
# Or, if you prefer yarn
$ yarn add @freshbooks/api @freshbooks/events @freshbooks/app
Your app will interact with the REST API using the Client
object, available from the @freshbooks/api
package.
The client is instantiated with a valid OAuth token, which is used throughout the lifetime of the client to make API calls.
import { Client } from '@freshbooks/api'
// Get token from authentication or configuration
const token = process.env.FRESHBOOKS_TOKEN
// Instantiate new FreshBooks API client
const client = new Client(token)
All REST API methods return a response in the shape of:
{
ok: boolean
data?: T // model type of result
error?: Error
}
Example API client call:
try {
// Get the current user
const { data } = await client.users.me()
console.log(`Hello, ${data.id}`)
} catch ({ code, message }) {
// Handle error if API call failed
console.error(`Error fetching user: ${code} - ${message}`)
}
If an endpoint supports searching or custom inclusions via query parameters, these parameters
can be specified using a QueryBuilderType
:
type QueryBuilderType = IncludesQueryBuilder | SearchQueryBuilder
An appropriate method on the API client will support an array of builders:
public readonly invoices = {
list: (accountId: string, queryBuilders?: QueryBuilderType[]) => Promise<Result<{
invoices: Invoice[];
pages: Pagination;
}>>;
}
The SearchQueryBuilder
supports the patterns: Equals
, In
, Like
and Between
.
Example API client call with SearchQueryBuilder
:
//create and populate SearchQueryBuilder
const searchQueryBuilder = new SearchQueryBuilder()
.like('address_like', '200 King Street')
.between('date', { min: new Date('2010-05-06'), max: new Date('2019-11-10') })
try {
// Get invoices matching search query
const { data } = await client.invoices.list('xZNQ1X', [searchQueryBuilder])
console.log('Invoices: ', data)
} catch ({ code, message }) {
// Handle error if API call failed
console.error(`Error fetching user: ${code} - ${message}`)
}
The IncludesQueryBuilder
simply requires the name of the key to be included.
Example API client call with IncludesQueryBuilder
:
//create and populate IncludesQueryBuilder
const includesQueryBuilder = new IncludesQueryBuilder().includes('lines')
try {
// Get invoices with included line items
const { data } = await client.invoices.list('xZNQ1X', [includesQueryBuilder])
console.log('Invoices: ', data)
} catch ({ code, message }) {
// Handle error if API call failed
console.error(`Error fetching user: ${code} - ${message}`)
}
Optional fields are specified in the API data model as optional
using Typescript's ?
operator, as well as marked as Nullable<T>
, where T
is the type of value, and Nullable
is a type definition to allow null
values.
// create a user model with required fields. set other fields as undefined
const user: User = {
id: '123',
firstName: 'Johnny',
lastName: 'Appleseed'
}
// explicity set an optional field
user.phoneNumbers = [
{
title: 'Home',
number: '555-555-5555'
}
]
// explicitly unset an optional field
user.phoneNumbers = null
If an API error occurs, the response object contains an error
object, with the following shape:
{
code: string
message?: string
}
If the endpoint is enabled for pagination, the response data
object contains the response model and a pages
property, with the following shape:
{
page: number
pages: number
total: number
size: number
}
Example request with pagination:
// Get list of invoices for account
const { invoices, pages } = await client.invoices.list('xZNQ1X')
// Print invoices
invoices.map(invoice => console.log(JSON.stringify(invoice)))
// Print pagination
console.log(`Page ${pages.page} of ${pages.total} pages`)
console.log(`Showing ${pages.size} per page`)
console.log(`${pages.size} total invoices`)
The FreshBooks SDK provides a pre-configured ExpressJS
app. This app provides OAuth2 authentication flow, a PassportJS
middleware for authenticating requests, and session middleware to retrieve tokens for a session.
Setting up the ExpressJS app requires a FreshBooks client__id
and client_secret
, as well as a callback URL to receive user authentication and refresh tokens. Once configured, routes can be configured as in any other ExpressJS
app.
import { Client } from '@freshbooks/api'
import createApp from '@freshbooks/app'
const CLIENT_ID = process.env.CLIENT_ID
const CLIENT_SECRET = process.env.CLIENT_SECRET
const CALLBACK_URL = process.env.CALLBACK_URL
const app = createApp(CLIENT_ID, CLIENT_SECRET, CALLBACK)
// set up callback route
app.get('/auth/freshbooks/redirect', passport.authorize('freshbooks')
// set up an authenticated route
app.get('/settings', passport.authorize('freshbooks'), async (req, res) => {
// get an API client
const { token } = req.user
const client = new Client(token)
// fetch the current user
try {
const { data } = await client.users.me()
res.send(data.id)
} catch ({ code, message }) {
res.status(500, `Error - ${code}: ${message}`)
}
})
npm test
HUSKY_SKIP_HOOKS=1 ./node_modules/.bin/lerna publish
Note: lerna publishing artifacts to github doesn't play too nicely with the git commit hooks installed by
husky. If you run into trouble, delete them from ./git/hooks/
and give it a try.
After the new tags have been pushed to github, log in there and create a release from one of the new tags to push to npm.