How to Develop a REST API on an 8base Workspace

Using serverless functions to stand up REST endpoints on an 8base Workspace

Getting Started Building a REST API on 8base

# If not authenticated, login via the CLI
$ 8base login
# Generate a new 8base project and select your desired workspace
$ 8base init rest-api-tutorial
# Move into the new directory
$ cd rest-api-tutorial

Generating the Serverless Functions for your REST API

# Our Index records endpoint
8base generate webhook getUsers — method=GET — path=’/users’ — syntax=js
# Our create record endpoint
8base generate webhook newUser — method=POST — path=’/users’ — syntax=js
# Our get record endpoint
8base generate webhook getUser — method=GET — path=’/users/{id}’ — syntax=js
# Our edit record endpoint
8base generate webhook editUser — method=PUT — path=’/users/{id}’ — syntax=js
# Our delete record endpoint
8base generate webhook deleteUser — method=DELETE — path=’/users/{id}’ — syntax=js

Directory Structure

Example directory structure for 8base project REST API functions

8base.yml file

functions:
listUsers:
type: webhook
handler:
code: src/webhooks/users/list/handler.js
path: /users
method: GET
getUser:
type: webhook
handler:
code: src/webhooks/users/get/handler.js
path: '/users/{id}'
method: GET
createUser:
type: webhook
handler:
code: src/webhooks/users/create/handler.js
path: /users
method: POST
editUser:
type: webhook
handler:
code: src/webhooks/users/edit/handler.js
path: '/users/{id}'
method: PUT
deleteUser:
type: webhook
handler:
code: src/webhooks/users/delete/handler.js
path: '/users/{id}'
method: DELETE

Writing our REST APIs Serverless Functions

GET User endpoint

/* Bring in any required imports for our function */
import gql from "graphql-tag";
import { responseBuilder } from "../utils";
/* Declare the Query that gets used for the data fetching */
const QUERY = gql`
query($id: ID!) {
user(id: $id) {
id
firstName
lastName
email
createdAt
updatedAt
avatar {
downloadUrl
}
roles {
items {
name
}
}
}
}
`;
module.exports = async (event, ctx) => {
/* Get the customer ID from Path Parameters */
let { id } = event.pathParameters;
let { user } = await ctx.api.gqlRequest(QUERY, { id });
if (!user) {
return responseBuilder(404, { message: `No record found.`, errors: [] });
}
return responseBuilder(200, { result: user });
};
$ mkdir src/webhooks/utils
$ touch src/webhooks/utils/index.js
/**
* Webhook response objects require a statusCode attribute to be specified.
* A response body can get specified as a stringified JSON object and any
* custom headers set.
*/
export const responseBuilder = (code = 200, data = {}, headers = {}) => {
/* If the status code is greater than 400, error! */
if (code >= 400) {
/* Build the error response */
const err = {
headers,
statusCude: code,
body: JSON.stringify({
errors: data.errors,
timestamp: new Date().toJSON(),
}),
}; /* Console out the detailed error message */
console.log(err); /* Return the err */
return err;
}
return {
headers,
statusCode: code,
body: JSON.stringify(data),
};
};

GET Users endpoint

import gql from 'graphql-tag'
import { responseBuilder } from '../utils'
const QUERY = gql`
query {
usersList {
count
items {
id
firstName
lastName
email
createdAt
updatedAt
}
}
}
`
module.exports = async (event, ctx) => {
/* Get the customer ID from Path Parameters */
let { usersList } = await ctx.api.gqlRequest(QUERY)
return responseBuilder(200, { result: usersList })
}

POST User endpoint

import gql from 'graphql-tag'
import { responseBuilder } from '../../utils'

const MUTATION = gql`
mutation($data: UserCreateInput!) {
userCreate(data: $data) {
id
email
firstName
lastName
updatedAt
createdAt
}
}
`
module.exports = async (event, ctx) => {
/**
* Here we're pulling data out of the request to
* pass it as the mutation input
*/

const { data } = event

try {
/* Run mutation with supplied data */
const { userCreate } = await ctx.api.gqlRequest(MUTATION, { data })

/* Success response */
return responseBuilder(200, { result: userCreate })
} catch ({ response: { errors } }) {

/* Failure response */
return responseBuilder(400, { errors })
}
}

PUT User endpoint

import gql from 'graphql-tag'
import { responseBuilder } from '../../utils'
const MUTATION = gql`
mutation($data: UserUpdateInput!) {
userUpdate(data: $data) {
id
email
firstName
lastName
updatedAt
createdAt
}
}
`
module.exports = async (event, ctx) => {
const { id } = event.pathParameters
/* Combine the pathParameters with the event data */
const data = Object.assign(event.data, { id })
try {
/* Run mutation with supplied data */
const { userUpdate } = await ctx.api.gqlRequest(MUTATION, { data })
/* Success response */
return responseBuilder(200, { result: userUpdate })
} catch ({ response: { errors } }) {
/* Failure response */
return responseBuilder(400, { errors })
}
}

DELETE User endpoint

import gql from 'graphql-tag'
import { responseBuilder } from '../../utils'
const MUTATION = gql`
mutation($id: ID!) {
userDelete(data: { id: $id }) {
success
}
}
`
module.exports = async (event, ctx) => {
const { id } = event.pathParameters
try {
/* Run mutation with supplied data */
const { userDelete } = await ctx.api.gqlRequest(MUTATION, { id })
/* Success response */
return responseBuilder(200, { result: userDelete })
} catch ({ response: { errors } }) {
/* Failure response */
return responseBuilder(400, { errors })
}
}

Testing our REST API locally

$ 8base invoke-local listUsers=> Result:
{
“headers”: {},
“statusCode”: 200,
“body”: “{\”result\”:{\”count\”:1,\”items\”:[{\”id\”:\”SOME_USER_ID\”,\”firstName\”:\”Fred\”,\”lastName\”:\”Scholl\”,\”email\”:\”freijd@iud.com\”,\”createdAt\”:\”2020–11–19T19:26:53.922Z\”,\”updatedAt\”:\”2020–11–19T19:46:59.775Z\”}]}}”
}
{
“pathParameters”: {
“id”: “[SOME_USER_ID]”
}
}
$ 8base invoke-local getUser -m request=> Result:
{
“headers”: {},
“statusCode”: 200,
“body”: “{\”result\”:{\”id\”:\”SOME_USER_ID\”,\”firstName\”:\”Fred\”,\”lastName\”:\”Scholl\”,\”email\”:\”freijd@iud.com\”,\”createdAt\”:\”2020–11–19T19:26:53.922Z\”,\”updatedAt\”:\”2020–11–19T19:46:59.775Z\”,\”avatar\”:null,\”roles\”:{\”items\”:[]}}}”
}
{
“data”: {
“firstName”: “Freddy”,
“lastName”: “Scholl”,
“email”: “my_new_email@123mail.com”
},
“pathParameters”: {
“id”: “SOME_USER_ID”
}
}
# Mock for a valid input for the editUser function
8base generate mock editUser — mockName success
# Mock for a invalid input for the editUser function
8base generate mock editUser — mockName failure
# Test an unsuccessful response
8base invoke-local editUser -m failure
=> Result:
{
headers: {},
statusCode: 400,
body: “{\”errors\”: [\r\n {\r\n \”message\”: \”Record for current filter not found.\”,\r\n \”locations\”: [],\r\n \”path\”: [\r\n \”userUpdate\”\r\n ],\r\n \”code\”: \”EntityNotFoundError\”,\r\n \”details\”: {\r\n \”id\”: \”Record for current filter not found.\”\r\n }\r\n }\r\n ],\r\n \”timestamp\”: \”2020–11–20T01:33:38.468Z\”\r\n}”
}

Deploying our REST API to 8base

8base CLI Describe Command for REST API

‍Wrap Up

Loves art, writing, and code.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store