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:
- Create a template project in the language you want to use.
- Modify the code to return a response.
- Update the
jellyspec.json
file - Test the function.
- 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:
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
:
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 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:
// 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
:
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
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:
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
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.)
{
"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:
{
"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):
If using VSCode it may look like this:
Once the command is running you can hit the endpoint with the following:
You will get the following (similar) result:
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.
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:
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:
or
or
If you list with -d
for details you will see something similar to this output:
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 --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:
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 aPOST
) - Check the curl command works.
- Ensure you have correctly copied the api location from the webui or the CLI.
- Ensure you have
https
nothttp
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 thekey
asjfwt
in lower case
Complete 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:
{
"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"
]
}