Skip to content

First Function creation

In this tutorial we are going to have a high level look at creating your own function and deploy it into the JellyFaaS eco system, ready for yourself and others to use. This is a quick overview guide, you can find a more in-depth guide here.

In this guide we are going to complete the following list:

  1. Create a template project in the language you want to use.
  2. Modify the code to return a response.
  3. Update the jellyspec.json file
  4. Test the function.
  5. Deploy the function.

For this to work you will need to ensure you have installed the JellyFaaS CLI (Command Line Interface), instructions can be found here.

Create a new project

We support the following languages for function creation : Go, Java, Ruby, Java, .DotNet, php, NodeJS and Python.

To create a project use the following command, in this case we are using Golang, but feel free to replace with a language you prefer, or just follow along using Go.

You will need to ensure you have the required software installed for using Go, for installing Golang see here, it is also useful to have an IDE such as VSCode. Download and install VSCode from here

Run the following command in a terminal:

jellyfaas create -d ~/src/functions/booksuggester -l go -n booksuggester

you should see output similar to this:

JellyFaaS CLI v1.2.2 - http://app.jellyfaas.com

Enumerating objects: 311, done.
Counting objects: 100% (311/311), done.
Compressing objects: 100% (168/168), done.
Total 311 (delta 197), reused 232 (delta 125), pack-reused 0 (from 0)

Project created successfully!
Please read the README.md for getting started.

You are now ready to edit the project, if you see any issues please see the section on help at the bottom of the page.

Editing the code

In this section we are going to edit the code to return a random book when supplied with a query param of a genre. Open the project in VSCode (or you editor of choice) and then open function.go:

VSCode of new project

What you can see here is a very simple function, with the init() function that describes the entry point, the actual function (in this case Example) and a response object Response struct along with a few imports at the top to make it work.

Add Data

First add a collection of books, this goes between the import section and the init section.

Information

If you get stuck the complete code is listed below.

Add this code into the function.go:

Book Structure and Data
// Book represents a single book with a title, author, and genre.
type Book struct {
 Title  string `json:"Title"`
 Author string `json:"Author"`
 Genre  string `json:"Genre"`
}

// books is a slice of Book with 20 entries of various genres.
var books = []Book{
 {Title: "Dune", Author: "Frank Herbert", Genre: "Science Fiction"},
 {Title: "Foundation", Author: "Isaac Asimov", Genre: "Science Fiction"},
 {Title: "Neuromancer", Author: "William Gibson", Genre: "Science Fiction"},
 {Title: "Pride and Prejudice", Author: "Jane Austen", Genre: "Romance"},
 {Title: "Jane Eyre", Author: "Charlotte Brontë", Genre: "Romance"},
 {Title: "The Notebook", Author: "Nicholas Sparks", Genre: "Romance"},
 {Title: "The Hobbit", Author: "J.R.R. Tolkien", Genre: "Fantasy"},
 {Title: "The Fellowship of the Ring", Author: "J.R.R. Tolkien", Genre: "Fantasy"},
 {Title: "Harry Potter and the Sorcerer's Stone", Author: "J.K. Rowling", Genre: "Fantasy"},
 {Title: "Harry Potter and the Chamber of Secrets", Author: "J.K. Rowling", Genre: "Fantasy"},
 {Title: "To Kill a Mockingbird", Author: "Harper Lee", Genre: "Classic"},
 {Title: "1984", Author: "George Orwell", Genre: "Classic"},
 {Title: "The Great Gatsby", Author: "F. Scott Fitzgerald", Genre: "Classic"},
 {Title: "Moby Dick", Author: "Herman Melville", Genre: "Classic"},
 {Title: "It", Author: "Stephen King", Genre: "Horror"},
 {Title: "Dracula", Author: "Bram Stoker", Genre: "Horror"},
 {Title: "Frankenstein", Author: "Mary Shelley", Genre: "Horror"},
 {Title: "Carrie", Author: "Stephen King", Genre: "Horror"},
 {Title: "Thinking, Fast and Slow", Author: "Daniel Kahneman", Genre: "Non-Fiction"},
 {Title: "Sapiens: A Brief History of Humankind", Author: "Yuval Noah Harari", Genre: "Non-Fiction"},
}

Add the search functionality

At the bottom of the file add the following function, this code takes a genre as a string and returns a book or an error

Copy this into the code at the end of the file:

Code for book searching
// getRandomBookByGenre takes a genre string and returns a 
// random Book matching that genre or an error if no 
// books are found for that genre.
func getRandomBookByGenre(genre string) (Book, error) {
 // Filter the books by the specified genre
 var filtered []Book
 for _, b := range books {
  if strings.ToLower(b.Genre) == genre {
   filtered = append(filtered, b)
  }
 }

 if len(filtered) == 0 {
  return Book{}, errors.New("no books found for the given genre")
 }

 return filtered[rand.Intn(len(filtered))], nil
}

you will also need to update the imports section if it doesn't automatically update to add math/rand time and errors:

updated imports
import (
 "encoding/json"
 "errors"
 "math/rand"
 "net/http"
 "strings"

 "github.com/GoogleCloudPlatform/functions-framework-go/functions"
)

Remove the response object

You will need to remove the response object, as we will be using the existing book object in this case: Typically, you use a separate response object to return, as this would be considered an antipattern or security issue

Delete response object
type Response struct {
 Greeting string `json:"greeting"`
}

Editing the entry point for the function

We now need to edit the actual code that JellyFaaS calls, this is the entry point of the function. In this getting started example, we will leave the function name as is, in further tutorials we will update and change this. This is just an internal reference or entry point, so changing to a unique name is not required.

Change the code to the following:

Updated Example function
func Example(w http.ResponseWriter, r *http.Request) {
 genre := r.URL.Query().Get("genre")

 book, err := getRandomBookByGenre(strings.ToLower(genre))
 if err != nil {
  http.Error(w, err.Error(), http.StatusNotFound)
  return
 }

 w.Header().Set("Content-Type", "application/json")
 w.WriteHeader(http.StatusOK)
 json.NewEncoder(w).Encode(book)
}

Changes made, feel free to skip to the next section

genre := r.URL.Query().Get("genre")`

Get the query param passed in called genre and set it to the variable genre

book, err := getRandomBookByGenre(genre)
 if err != nil {
  http.Error(w, err.Error(), http.StatusNotFound)
  return
 }

Call out to the function getRandomBookByGenre with the param genre, this function returns a valid book choice, or an error. If an error returns set a HTTP error with the status of http.StatusNotFound or 404 internally.

 w.Header().Set("Content-Type", "application/json")
 w.WriteHeader(http.StatusOK)
 json.NewEncoder(w).Encode(book)

If all is ok, then set the correct response type to JSON and set http.StatusOk (200) and encode the book into json and then return.

Updating the jellyspec.json file

Before we can deploy or test we need to update the jellyspec.json file. For a deepdive on the jellyfaas.json file see here. The jellyfaas.json file contains information and specifications about the function. Like most json specification files, it may look complex, but the data is quite simple, and furthermore, most of the 'complex' looking data is autogenerated from the cli.

At a top level view the jellyspec.json contains information such as the name of the function, the entry points and specification about the requirements of calling the function. This allows JellyFaaS to validated requirements, and also allows the functions to work with AI (AI needs to know how the API 'looks' so it knows how to call it correctly.)

The Template `jellyspec.json` file
{
  "name": "Example Project",
  "$jellyspecversion": "v1.0.0",
  "shortname": "example",
  "runtime": "go122",
  "entrypoint": "Example",
  "description": "Takes a name as query and greets you!",
  "requirements": {
    "requestType": "GET",
    "inputType": null,
    "outputType": null,
    "queryParams": [
      {
        "name": "name",
        "required": true,
        "description": "Name of person",
        "exampleData": "name=foo"
      }
    ],
    "output
    "outputSchema": {
      "$schema": "<https://json-schema.org/draft/2020-12/schema>",
      "properties": {
        "greeting": {
          "type": "string"
        }
      },
      "type": "object",
      "title": "Generated schema from jellyfaas"
    },
    "outputJsonExample": "{\"greeting\":\"hello, foo!\"}"
  },
  "tags": [
    "template"
  ]
}

We need to change it to this:

The Template `jellyspec.json` file
{
  "name": "Book Suggester",
  "$jellyspecversion": "v1.0.0",
  "shortname": "booksuggester",
  "runtime": "go122",
  "entrypoint": "Example",
  "description": "Takes a genre and returns a suggested book",
  "requirements": {
    "requestType": "GET",
    "queryParams": [
      {
        "name": "genre",
        "required": true,
        "description": "Book genre, for example 'fantasy' or 'horror'",
        "exampleData": "genre=romance"
      }
    ],
    "outputSchema": {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "properties": {
        "title": {
          "type": "string"
        },
        "author": {
          "type": "string"
        },
        "genre": {
          "type": "string"
        }
      },
      "type": "object",
      "title": "Generated schema from jellyfaas"
    },
    "outputJsonExample": "{\"title\":\"The Fellowship of the Ring\",\"author\":\"J.R.R. Tolkien\",\"genre\":\"Fantasy\"}"
  },
  "tags": [
    "tutorial"
  ]
}

Warning

You will need to provide a unique shortname, you can check if using the following CLI command jellyfaas exists -n <shortname> to see if the name is already taken, or search via the app.

A quick overview shows the function required query params, and the file also defines the outputschema as JSON, these are optional, however allow AI to use these functions more accurately, and allow the JellyFaaS function pages to produce sample code and structures.

Testing the function locally

Before we deploy, testing locally is advised, this way you can test, debug and run your function. All languages support local testing. For more details on how to test on each language see here

To run locally just run the following command in a terminal in VSCode (for example):

FUNCTION_TARGET=Example LOCAL_ONLY=true go run cmd/main.go

If using VSCode it may look like this:

VSCode of new project

Once the command is running you can hit the endpoint with the following:

curl --location 'localhost:8080?genre=fantasy'

You will get the following (similar) result:

{
    "title": "The Fellowship of the Ring",
    "author": "J.R.R. Tolkien",
    "genre": "Fantasy"
}

You can then press CTRL-C in the terminal to end the test.

Deploying the function

To deploy the function into JellyFaaS you just need to run the following command. Note, you need to be one level up from code, so you can see the directory.

jellyfaas zip -s booksuggester -o true -d true

In short this zips up the -s source directory booksuggester, -o overwrites the zip if it already exists and then -d deploys true.

You can optionally wait for deployment with the -w true flag

You should then see the following output:

Output from deploy
JellyFaaS CLI v1.2.2 - http://app.jellyfaas.com 

Directory zipped successfully!
Zip file: booksuggester.zip
Ready for deploy

        Deploying function booksuggester.zip
        Function URL: https://app.jellyfaas.com/function/booksuggester
        API Endpoint: https://api.jellyfaas.com/booksuggester-cn16g6rdervlrpsg3sr0-1-s
        Function is a new function, and is currently deploying.
Done...

Deploying can take up to five minutes.

To check if your application has deployed, you can use the CLI commands:

jellyfaas library list

or

jellyfaas library list |grep <shortname>

or

jellyfaas library list -d <shortname>

If you list with -d for details you will see something similar to this output:

Details output for booksuggester
JellyFaaS CLI v1.2.2 - http://app.jellyfaas.com

Function Name: Book Suggester
Function ID: booksuggester
Owner: admin
Owner Description:
Version Count: 1
Created At: Fri, 10 Jan 2025 13:57:18 UTC
Updated At: Fri, 10 Jan 2025 13:57:18 UTC
Versions:
Description: Takes a genre and returns a suggested book
Entry Point: Example
  Version: 1
  Latest: true
    FunctionId: booksuggester-cn16g6rdervlrpsg3sr0-1-s
    Function URL: https://app.jellyfaas.com/function/booksuggester
    URL: https://api.jellyfaas.com/booksuggester-cn16g6rdervlrpsg3sr0-1-s/booksuggester
  Release Date: Fri, 10 Jan 2025 13:57:18 UTC
  Runtime: go122
  Readme File: Found
  ChangeLog File: found

  Requirements:
    Request Type: GET
     Query Param: genre, Required: true
    Output Schema: {"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{"title":{"type":"string"},"author":{"type":"string"},"genre":{"type":"string"}},"type":"object","title":"Generated schema from jellyfaas"}
    Output JSON Example: {"title":"The Fellowship of the Ring","author":"J.R.R. Tolkien","genre":"Fantasy"}

You can also visit the web app (via the WebUI link) to see the function details.

Testing the function

If you completed the quick start guides you will remember you need a JFWT (JellyFaaS Web Token), much like a JWT (Json Web Token) to call any function that is running in the platform, for more details on this see here

Getting a token, via REST:

Curl Command to get a token
curl --location 'https://api.jellyfaas.com/auth-service/v1/validate' --header 'x-jf-apikey: <my secret key>'

Use the token to call out to your new function on JellyFaaS

curl --location 'https://api.jellyfaas.com/booksuggester-cn16g6rdervlrpsg3sr0-1-s/booksuggester?genre=horror' \
--header 'jfwt: <jfwt token>'

You will then get a response back like:

{
    "title": "Carrie",
    "author": "Stephen King",
    "genre": "Horror"
}

Help, its not working

The code will not compile run:

  • Check the code is correct, run a diff on the complete code output below and see what happens.
  • See if a basic hello world program compiles.

It fails to Deploy:

  • Check the jellyspec file is correct.
  • Check the error message returned from the CLI deploy command
  • Check the name is unique, two users cannot deploy the app with the same name.

If you get 404's or another error code these are the top things to check when calling out:

  • You are doing a GET request
  • If using Postman or another UI, you don't have any extra headers, or have anything in the BODY set (this changes it to a POST)
  • Check the curl command works.
  • Ensure you have correctly copied the api location from the webui or the CLI.
  • Ensure you have https not http set
  • Check your token is used NOT the apikey (that is used to get a token)
  • Check you have just copied the Token into the header as the value, with the key as jfwt in lower case

Complete Code

Complete working code
package example

import (
 "encoding/json"
 "errors"
 "math/rand"
 "net/http"
 "strings"

 "github.com/GoogleCloudPlatform/functions-framework-go/functions"
)

// Book represents a single book with a title, author, and genre.
type Book struct {
 Title  string `json:"title"`
 Author string `json:"author"`
 Genre  string `json:"genre"`
}

// books is a slice of Book with 20 entries of various genres.
var books = []Book{
 {Title: "Dune", Author: "Frank Herbert", Genre: "Science Fiction"},
 {Title: "Foundation", Author: "Isaac Asimov", Genre: "Science Fiction"},
 {Title: "Neuromancer", Author: "William Gibson", Genre: "Science Fiction"},
 {Title: "Pride and Prejudice", Author: "Jane Austen", Genre: "Romance"},
 {Title: "Jane Eyre", Author: "Charlotte Brontë", Genre: "Romance"},
 {Title: "The Notebook", Author: "Nicholas Sparks", Genre: "Romance"},
 {Title: "The Hobbit", Author: "J.R.R. Tolkien", Genre: "Fantasy"},
 {Title: "The Fellowship of the Ring", Author: "J.R.R. Tolkien", Genre: "Fantasy"},
 {Title: "Harry Potter and the Sorcerer's Stone", Author: "J.K. Rowling", Genre: "Fantasy"},
 {Title: "Harry Potter and the Chamber of Secrets", Author: "J.K. Rowling", Genre: "Fantasy"},
 {Title: "To Kill a Mockingbird", Author: "Harper Lee", Genre: "Classic"},
 {Title: "1984", Author: "George Orwell", Genre: "Classic"},
 {Title: "The Great Gatsby", Author: "F. Scott Fitzgerald", Genre: "Classic"},
 {Title: "Moby Dick", Author: "Herman Melville", Genre: "Classic"},
 {Title: "It", Author: "Stephen King", Genre: "Horror"},
 {Title: "Dracula", Author: "Bram Stoker", Genre: "Horror"},
 {Title: "Frankenstein", Author: "Mary Shelley", Genre: "Horror"},
 {Title: "Carrie", Author: "Stephen King", Genre: "Horror"},
 {Title: "Thinking, Fast and Slow", Author: "Daniel Kahneman", Genre: "Non-Fiction"},
 {Title: "Sapiens: A Brief History of Humankind", Author: "Yuval Noah Harari", Genre: "Non-Fiction"},
}

func init() {
 functions.HTTP("Example", Example)
}

func Example(w http.ResponseWriter, r *http.Request) {
 genre := r.URL.Query().Get("genre")

 book, err := getRandomBookByGenre(strings.ToLower(genre))
 if err != nil {
  http.Error(w, err.Error(), http.StatusNotFound)
  return
 }

 w.Header().Set("Content-Type", "application/json")
 w.WriteHeader(http.StatusOK)
 json.NewEncoder(w).Encode(book)
}

// getRandomBookByGenre takes a genre string and returns a random Book matching that genre
// or an error if no books are found for that genre.
func getRandomBookByGenre(genre string) (Book, error) {
 // Filter the books by the specified genre
 var filtered []Book
 for _, b := range books {
  if strings.ToLower(b.Genre) == genre {
   filtered = append(filtered, b)
  }
 }

 if len(filtered) == 0 {
  return Book{}, errors.New("no books found for the given genre")
 }

 return filtered[rand.Intn(len(filtered))], nil
}

jellyspec file:

jellyspec.json
{
  "name": "Book Suggester",
  "$jellyspecversion": "v1.0.0",
  "shortname": "booksuggester",
  "runtime": "go122",
  "entrypoint": "Example",
  "description": "Takes a genre and returns a suggested book",
  "requirements": {
    "requestType": "GET",
    "queryParams": [
      {
        "name": "genre",
        "required": true,
        "description": "Book genre, for example 'fantasy' or 'horror'",
        "exampleData": "genre=romance"
      }
    ],
    "outputType": "JSON",
    "outputSchema": {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "properties": {
        "title": {
          "type": "string"
        },
        "author": {
          "type": "string"
        },
        "genre": {
          "type": "string"
        }
      },
      "type": "object",
      "title": "Generated schema from jellyfaas"
    },
    "outputJsonExample": "{\"title\":\"The Fellowship of the Ring\",\"author\":\"J.R.R. Tolkien\",\"genre\":\"Fantasy\"}"
  },
  "tags": [
    "tutorial"
  ]
}