REST API example with Express.js
This tutorial will guide you through the process of creating a REST API with Express.js that uses the Golem Network to process data. The goal is to show you how to use the Job API in a real-world scenario.
What will you build?
You will create a simple REST API that will allow you to send some text to the Golem Network and get back a text-to-speech result in the form of a WAV file.
Prerequisites
This tutorial assumes that you have already installed Yagna and have it running in the background. If you haven't done so yet, please follow the instructions in this tutorial before proceeding.
Setting up the project
First, create a new directory for your project and initialize a new Node.js project in it:
mkdir golem-express
cd golem-express
npm init -y
npm install @golem-sdk/golem-js express
Creating the API
Let's start by creating a new file called index.mjs
and pasting the following code into it:
import express from 'express'
const app = express()
const port = 3000
app.use(express.text())
app.get('/', (req, res) => {
res.send('Hello World!')
})
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
Start your app by running node index.mjs
and run curl http://localhost:3000
in another terminal window. You should see the following output:
Hello World!
So far, so good! Stop the app by pressing Ctrl+C
and let's move on to the next step.
Connecting to the Golem Network
Now let's connect to the Golem Network. First, import the GolemNetwork
class from @golem-sdk/golem-js
and create a new instance of it. If you're feeling adventurous, you can create your own image and install espeak on it, but to save time we will use the image severyn/espeak:latest
provided by one of our community members.
import { GolemNetwork } from '@golem-sdk/golem-js'
const golem = new GolemNetwork({
image: 'severyn/espeak:latest',
yagnaOptions: { apiKey: 'try_golem' },
})
golem
.init()
.then(() => {
console.log('Connected to the Golem Network!')
})
.catch((err) => {
console.error('Error connecting to the Golem Network:', err)
process.exit(1)
})
Let's also add a handler for the SIGINT
signal so that we can close the connection to the Golem Network when the app is stopped.
process.on('SIGINT', async () => {
await golem.close()
process.exit(0)
})
Creating a retrievable task
Let's add a new endpoint to our API that will take some text from the request body and create a new task on the Golem Network. To do that, we will use the createJob()
method. This method will give us a Job
object that we can use to get the state of the job and its results later. On the provider side, we will run the espeak
command to convert the text to speech, save it to a .wav
file and download that file to your local file system with the downloadFile()
method. We will give the file a random name to avoid collisions.
app.post('/tts', async (req, res) => {
if (!req.body) {
res.status(400).send('Missing text parameter')
return
}
const job = await golem.createJob(async (ctx) => {
const fileName = `${Math.random().toString(36).slice(2)}.wav`
await ctx
.beginBatch()
.run(`espeak "${req.body}" -w /golem/output/output.wav`)
.downloadFile('/golem/output/output.wav', `public/${fileName}`)
.end()
return fileName
})
res.send(`Job started! ID: ${job.id}`)
})
Getting the job state
Now let's add another endpoint that will allow us to get the state of the job. We will use the fetchState()
method of the Job
object to get the state of the job and return it to the user.
app.get('/tts/:id', async (req, res) => {
const job = golem.getJobById(req.params.id)
try {
const state = await job.fetchState()
res.send(state)
} catch (err) {
res.status(404).send('Job not found')
}
})
Getting the job results
Finally, let's add an endpoint that will allow us to get the results of the job. We will use the fetchResults()
method of the Job
object to get the results of the job, and return them to the user. We will also serve the files from the /public
directory, so that the user can easily listen to the results in a browser.
// serve files in the /public directory
app.use('/results', express.static('public'))
app.get('/tts/:id/results', async (req, res) => {
const job = golem.getJobById(req.params.id)
try {
const results = await job.fetchResults()
res.send(
`Job completed successfully! Open the following link in your browser to listen to the result: http://localhost:${port}/results/${results}`
)
} catch (err) {
res.status(404).send('Job not found')
}
})
Testing the app
We're done with the code! Let's start the app and test it.
First, let's start the server:
node index.mjs
Sending a POST request
Open a new terminal window. Let's send a POST request to the /tts
endpoint. Feel free to replace Hello Golem
with any text you want:
curl \
--header "Content-Type: text/plain" \
--request POST \
--data "Hello Golem" \
http://localhost:3000/tts
You should see the output:
Job started! ID: <job_id>
Make sure to write down the job ID, as we will need it in the next step.
Sending a GET request to get the job state
Now let's send a GET request to the /tts/<job_id>
endpoint to get the state of the job:
curl http://localhost:3000/tts/<job_id>
You should see the output:
pending
Wait a few seconds and send the same request again. You should see the output:
done
Sending a GET request to get the job results
Finally, let's send a GET request to the /tts/<job_id>/results
endpoint to get the results of the job:
curl http://localhost:3000/tts/<job_id>/results
You should see the output:
Job completed successfully! Open the following link in your browser to listen to the result: http://localhost:3000/results/<file_name>
Open the link in your browser, and you should hear the text you sent to the API!
Congratulations! You have just created a REST API that uses the Golem Network to process data! 🎉
Full code
Here's the full code of the index.mjs
file:
import express from 'express'
import { GolemNetwork } from '@golem-sdk/golem-js'
const app = express()
const port = 3000
app.use(express.text())
const golem = new GolemNetwork({
image: 'severyn/espeak:latest',
yagnaOptions: { apiKey: 'try_golem' },
})
golem
.init()
.then(() => {
console.log('Connected to the Golem Network!')
})
.catch((err) => {
console.error('Error connecting to the Golem Network:', err)
process.exit(1)
})
app.post('/tts', async (req, res) => {
if (!req.body) {
res.status(400).send('Missing text parameter')
return
}
const job = await golem.createJob(async (ctx) => {
const fileName = `${Math.random().toString(36).slice(2)}.wav`
await ctx
.beginBatch()
.run(`espeak "${req.body}" -w /golem/output/output.wav`)
.downloadFile('/golem/output/output.wav', `public/${fileName}`)
.end()
return fileName
})
res.send(`Job started! ID: ${job.id}`)
})
app.get('/tts/:id', async (req, res) => {
const job = golem.getJobById(req.params.id)
try {
const state = await job.fetchState()
res.send(state)
} catch (err) {
res.status(404).send('Job not found')
}
})
// serve files in the /public directory
app.use('/results', express.static('public'))
app.get('/tts/:id/results', async (req, res) => {
const job = golem.getJobById(req.params.id)
try {
const results = await job.fetchResults()
res.send(
`Job completed successfully! Open the following link in your browser to listen to the result: http://localhost:${port}/results/${results}`
)
} catch (err) {
res.status(404).send('Job not found')
}
})
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
process.on('SIGINT', async () => {
await golem.close()
process.exit(0)
})