Lab 2 - Building a QR Generator¶
Following on from Lab 1, this lab will extend what we learnt to leverage npm packages to extend what we can do with a Worker. The topics that will be covered in this lab are as follows:
Generating a Worker locally
Importing a npm module into our Worker’s Script
Publishing the Worker to the Edge
How to rollback changes using Deployments
Exploring Debugging options available for Workers
Generating a Worker with Wrangler¶
Creating a simple Hello World Worker¶
The Worker we will deploy today will generate a QR code that will point to https://workers.dev. We’ll continue using the Module Worker Syntax.
Wrangler Installed and Authenticated
Please ensure Wrangler is installed locally and authenticated as described in the Wrangler setup guide
So let’s get started:
From your terminal navigate to a directory where you’d like to host your Worker’s files, and then follow the steps below:
Generate a “Hello World” skeleton by initialising a basic Worker project with the following command and options:
npm create cloudflare@latest -- lab2
What type of application do you want to create?
type "Hello World" script
Do you want to use TypeScript?
typescript no
Do you want to deploy your application?
│ no
APPLICATION CREATED Deploy your application with npm run deploy
The above command has initialized a basic Worker’s project, with the following directory structure at the top level:
tree -L 1
.
├── node_modules
├── package-lock.json
├── package.json
├── src
└── wrangler.toml
3 directories, 3 files
The src
directory contains the JavaScript for your Worker, and will be the file we use to write our code to. The second file of interest is wrangler.toml
at the top level, this file is used to inform Wrangler which accounts, which bindings, and custom routes we’d like to use for our project. We will modify this file throughout this lab as required.
Next let’s change into the directory, and startup Visual Studio Code or another text editor of your choice.
cd lab2
code .
For this lab we will leverage the npm module, qr-image, which we will use to take text and encode it into a QR code. The package supports generating the QR codes in a number of file formats, and configuring other aspects of the QR code generated. So let’s get started by installing the package for use by your project:
npm install --save qr-image
Next in the file
worker.js
file in the./src
directory add the following two lines to the top of your file:
const qr = require('qr-image');
const url = "https://workers.dev"
Next add the
generateQRCode()
function to your script. This function receives the text from the page via aPOST
, and will return the image created using theqr-image
module.
async function generateQRCode(request) {
const { text } = await request.json()
const headers = { "Content-Type": "image/png" }
const qr_png = qr.imageSync(text || url)
return new Response(qr_png, { headers });
}
Add the landing page code, that you’ll use to send the request to the Worker, and to see the generated QR code.
const landing = `
<h1>QR Generator</h1>
<p>Click the below button to generate a new QR code. This will make a request to your Worker.</p>
<input type="text" id="text" value="https://workers.dev"></input>
<button onclick="generate()">Generate QR Code</button>
<p>Generated QR Code Image</p>
<img id="qr" src="#" />
<script>
function generate() {
fetch(window.location.pathname, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text: document.querySelector("#text").value })
})
.then(response => response.blob())
.then(blob => {
const reader = new FileReader();
reader.onloadend = function () {
document.querySelector("#qr").src = reader.result; // Update the image source with the newly generated QR code
}
reader.readAsDataURL(blob);
})
}
</script>
`
Lastly, let’s update the default fetch to listen for the
POST
event and send that request to thegenerateQRCode()
function.
export default {
async fetch(request, env, ctx) {
if (request.method === 'POST') {
return generateQRCode(request)
}
return new Response(landing, {
headers: {
"Content-Type": "text/html"
}
})
},
};
As we depend on an external Node library, we can either use a subset of Node.js APIs available in the Workers runtime, or add polyfills for a subset of Node.js APIs to our code. For the
qr-image
library, we’ll add polyfills using Wrangler. To do so, add the following line to yourwrangler.toml
file.
node_compat = true
Check your results locally with
wrangler.dev
and when you’re happy to deploy to Cloudflare, executewrangler deploy
. Test it works and continue to the remaining parts of this lab.
Full code snippet for the worker.js
script is as follows
const qr = require('qr-image');
const url = "https://workers.dev"
export default {
async fetch(request, env, ctx) {
if (request.method === 'POST') {
return generateQRCode(request)
}
return new Response(landing, {
headers: {
"Content-Type": "text/html"
}
})
},
};
async function generateQRCode(request) {
const { text } = await request.json()
const headers = { "Content-Type": "image/png" }
const qr_png = qr.imageSync(text || url)
return new Response(qr_png, { headers });
}
const landing = `
<h1>QR Code Generator</h1>
<p>Click the below button to generate a new QR code. This will make a request to your Worker.</p>
<input type="text" id="text" value=${url}></input>
<button onclick="generate()">Generate QR Code</button>
<p>Generated QR Code Image</p>
<img id="qr" src="#" alt='QR Code'/>
<script>
function generate() {
fetch(window.location.pathname, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text: document.querySelector("#text").value })
})
.then(response => response.blob())
.then(blob => {
const reader = new FileReader();
reader.onloadend = function () {
document.querySelector("#qr").src = reader.result; // Update the image source with the newly generated QR code
}
reader.readAsDataURL(blob);
})
}
</script>
`
Preview of the generated page should look like:
Deployments¶
Deployments are a log of static historical versions of your Worker. They track changes to the bundled code, bindings, compatibility date, and usage model associated with a Worker over time. They also keep metadata associated with the deployment including the user, deploy source, timestamp, and other useful information to understand, and audit who or what is making changes to your Worker.
Key points to remember:
Deployments can be viewed from the Cloudflare Dashboard, and Wrangler
You can view the last 10 deployments, and rollback to any of the last 10
Resources attached to your Worker, like KV, R2 and Durable Objects are not tracked with deployments
New deployments are created whenever an upload, binding change, usage change, or rollback is made
Rollbacks do not effect the local development environment, therefore make sure you use version control for your Worker
Rollbacks are not allowed if an external resource is deleted or modified between the current version and target
Let’s first view our current deployment’(s) for the lab2 Worker, by executing the command:
wrangler deployments list
⛅️ wrangler 3.3.0 (update available 3.4.0)
-----------------------------------------------------
🚧`wrangler deployments` is a beta command. Please report any issues to https://github.com/cloudflare/workers-sdk/issues/new/choose
Deployment ID: 6f550aa5-192e-4839-84c9-1eeddd75a8ea
Created on: 2023-07-28T02:56:40.365674Z
Author: tsangha@cloudflare.com
Source: Upload from Wrangler 🤠
🟩 Active
As you can see I have a single deployment, let’s change that by making a small change to the heading (h1
) of our HTML page in the workers.js
script and re-deploying using the wrangler deploy
command.
<h1>QR Code Generator</h1>
changes to:
<h1>QR Code Generator ver 2.0</h1>
After the changes have been deployed execute the list to see the new deployment:
wrangler deployments list
⛅️ wrangler 3.3.0 (update available 3.4.0)
-----------------------------------------------------
🚧`wrangler deployments` is a beta command. Please report any issues to https://github.com/cloudflare/workers-sdk/issues/new/choose
Deployment ID: 6f550aa5-192e-4839-84c9-1eeddd75a8ea
Created on: 2023-07-28T02:56:40.365674Z
Author: tsangha@cloudflare.com
Source: Upload from Wrangler 🤠
Deployment ID: 0f474c33-0276-45c2-bcc5-40cf3eca61eb
Created on: 2023-07-28T03:11:50.843399Z
Author: tsangha@cloudflare.com
Source: Upload from Wrangler 🤠
🟩 Active
Now let’s execute a Rollback to the previous version with the deployment-id
as required:
wrangler rollback [deployment-id]
wrangler rollback 6f550aa5-192e-4839-84c9-1eeddd75a8ea
⛅️ wrangler 3.3.0 (update available 3.4.0)
-----------------------------------------------------
🚧`wrangler rollback` is a beta command. Please report any issues to https://github.com/cloudflare/workers-sdk/issues/new/choose
✔ This deployment 6f550aa5 will immediately replace the current deployment and become the active deployment across all your deployed routes and domains. However, your local development environment will not be affected by this rollback. Note: Rolling back to a previous deployment will not rollback any of the bound resources (Durable Object, R2, KV, etc.). … yes
✔ Please provide a message for this rollback (120 characters max) … lab2-test
Successfully rolled back to Deployment ID: 6f550aa5-192e-4839-84c9-1eeddd75a8ea
Current Deployment ID: 2d6a1525-b142-452f-8648-b8a22fc75a3d
Revisit your webpage, and you should see the heading has been reverted.
Debugging¶
In this debugging section, we will explore how to leverage wrangler tail
and Tail Workers to send logs to a HTTP endpoint for debugging, which are two components of the debugging tools we have built into the platform.
Wrangler Tail¶
To access logs and exceptions from your Workers, one of the most convenient methods to do so is by using wrangler tail
. This allows you livestream logs from any Worker straight to your terminal.
Cloudflare Dashboard
You are also able to livestream logs to your Cloudflare dashboard on-demand if wrangler
is not available
Let’s begin by adding a custom log statement to our lab2 Worker’s code, so we can see something meaningful. Using the
console.log
statement, let’s add the following code to ourasync fetch
code block in theworkers.js
script.
export default {
async fetch(request, env, ctx) {
const cf = request.cf
console.log(`This is a live log event that displays my current city location as ${cf.city}`)
if (request.method === 'POST') {
return generateQRCode(request)
}
...
Re-deploy your worker and start a tail session
wrangler deploy
wrangler tail
wrangler tail
⛅️ wrangler 3.3.0 (update available 3.4.0)
-----------------------------------------------------
Successfully created tail, expires at 2023-07-28T09:35:15Z
Connected to lab2, waiting for logs...
Navigate in your browser to your Worker, and you should now see the following logs appear:
GET https://lab2.apjc-dev.workers.dev/ - Ok @ 28/07/2023, 1:41:24 pm
(log) This is a live log event that displays my current city location as Sydney
GET https://lab2.apjc-dev.workers.dev/ - Ok @ 28/07/2023, 1:41:24 pm
(log) This is a live log event that displays my current city location as Sydney
GET https://lab2.apjc-dev.workers.dev/favicon.ico - Ok @ 28/07/2023, 1:41:24 pm
(log) This is a live log event that displays my current city location as Sydney
Tail any Worker
To tail a different Worker in your account, the command syntax is as follows: wrangler tail [worker-name]
Tail Workers (optional)¶
Tail Workers is an optional part of this lab, and is available on Workers Paid on Enterprise Tiers only. A Tail Worker receives information about the execution of other Workers, such as:
HTTP statuses
console.log()
messagesuncaught exceptions
A Tail Worker is tied to another Worker known as the producer Worker (your primary application Worker) that the Tail Worker will track as seen in the diagram below:
The Tail Worker will capture all events associated witht he producer Worker once it has finished executing.
In this section of the lab, we’ll configure a Tail Worker to receive logs from our Lab 2 Worker which will be the producer now, and put those logs into Workers KV. You could send the logs received by the Tail Worker to a different endpoint as well.
The diagram below depicts what we’ll create:
Create a Tail Worker¶
Let’s now create a new Worker in your source directory, called lab2_tail_worker. Follow the instructions in the first section to get started. Once you have the Hello World skelton setup, modify the worker.js
script in the src
directory, and copy the following code snippet:
export default {
async tail(events, env, ctx) {
/*
timestamp = key value for KV
logMessage = console.log() message from producer Worker
put values for every request into the same keyvalue store
*/
const timestamp = events[0].eventTimestamp
const logMessage = JSON.stringify(events[0].logs[0].message)
const setCache = (key, data) => env.lab2_tail_worker_kv.put(key, data)
await setCache(timestamp, logMessage)
}
}
After you’ve done that and saved the file, create a KV namespace called: lab2_tail_worker_kv
. The Wrangler command is as follows:
wrangler kv:namespace create "lab2_tail_worker_kv"
The last step is to configure the Tail Worker’s wrangler.toml
file to use the KV namespace we created by adding a binding. Ensure you substitute the id
for your specific environment. The KV binding should look as follows:
kv_namespaces = [
{ binding = "lab2_tail_worker_kv", id = "MY_UNIQUE_ID"}
]
Save your Tail Worker files and deploy using the command: wrangler deploy
.
After deploying your Worker, the last remaining step is to add the following line to your original lab2 Worker’s wrangler.toml
file.
tail_consumers = [{service = "lab2_tail_worker", environment = "production"}]
Make sure you execute wrangler deploy
on the lab2 Worker for the changes to take effect. Once you’ve done that, visit the URL for your lab2 Worker to generate entries to your KV namespace.
Once done, you should see entries on your KV namespace like the following Wrangler command or figure:
wrangler kv:key list --namespace-id=MY_UNIQUE_ID
[
{
"name": "1691380491029"
},
{
"name": "1691380491328"
},
{
"name": "1691380491411"
},
{
"name": "1691380494113"
},
{
"name": "1691380507591"
}
]