Having Coffee with Deno - Dynamic Names
Welcome to the second installment of our Deno series, where we build a script that pairs up people for coffee.
In the last post, we built a script that helped the Justice League meet up for coffee.
As of right now, our script looks like the following.
Even though this approach works, the major problem is that every time there's a member change in the Justice League (which seems to happen more often than not), we have to go back and update the list manually.
It'd be better if we could get this list dynamically instead. Given that the League are great developers, they have their own GitHub organization. Let's work on integrating with GitHub's API to get the list of names.
To get the list of names from GitHub, we'll need to do the following.
- First, we need to figure out which GitHub endpoint will give us the members of the League. This, in turn, will also tell us what permissions we need for our API scope.
- Now that we have a secret, we need to update our script to read from an
- Once we have the secret being read, we can create a function to retrieve the members of the League.
- Miscellaneous refactoring of the main script to handle a function returning complex types instead of strings.
Laying the Foundation
Before we start, we should reactor our current file. It works, but we have a mix of utility functions (
createPairsFrom) combined with presentation functions (
createMessage). Let's go ahead and move
createPairsFrom to their own module.
With these changes, we can update
index.ts to be:
Now that our code is more tidy, we can focus on figuring out which GitHub endpoint(s) to use to figure out the members of the Justice League.
Taking a look at the docs, we see that there are two different options.
What's the difference between the two? In GitHub parlance, an Organization is an overarching entity that consists of members which, in turn, can be part of multiple teams.
Using the Justice League as an example, it's an organization that contains Batman, and Batman can be part of the Justice League Founding Team and a member of the Batfamily Team.
Since we want to pair everyone up in the Justice League, we'll use the Get members of an Organization approach.
Working with Secrets
To interact with the endpoint, we'll need to create an API token for GitHub. Looking over the docs, our token needs to have the
read:org scope. We can create this token by following the instructions here about creating a Personal Auth Token (PAT).
Once we have the token, we can invoke the endpoint with cURL or Postman to verify that we can communicate with the endpoint correctly.
After we've verified, we'll need a way to get this API token into our script. Given that this is sensitive information, we absolutely should NOT check this into the source code.
Creating an ENV File
A common way of dealing with that is to use an .env file which doesn't get checked in, but our application can use it during runtime to get secrets.
Let's go ahead and create the
.env file and put our API token here.
Our problem now is that if we check
git status, we'll see this file listed as a change. We don't want to check this in, so let's add a
Adding a .gitignore File
.env file created, we need to create a .gitignore file, which tells Git not to check in certain files.
Let's go ahead and add the file. You can enter the below, or you can use the Node gitignore file (found here)
We can validate that we've done this correctly if we run
git status and don't see
.env showing up anymore as a changed file.
Loading Our Env File
Now that we have the file created, we need to make sure that this file loads at runtime.
index.ts file, let's make the following changes.
When we run the script now with
deno run, we get an interesting prompt:
Deno requests read access to ".env".
- Requested by `Deno.readFileSync()` API.
- Run again with --allow-read to bypass this prompt
This is one of the coolest parts about Deno; it has a security system that prevents scripts from doing something that you hadn't intended through its Permissions framework.
For example, if you weren't expecting your script to read from the
env file, it'll prompt you to accept. Since packages can be taken over and updated to do nefarious things, this is a terrific idea.
The permissions can be tuned (e.g., you're only allowed to read from the .env file), or you can give blanket permissions. In our cases, two resources are being used (the ability to read the
.env file and the ability to read the
GITHUB_BEARER_TOKEN environment variable).
Let's run the command with the
deno run --allow-run --allow-env ./index.ts
If the bearer token gets printed, we've got the
.env file created correctly and can proceed to the next step.
Let's Get Dynamic
Now that we have the bearer token, we can work on calling the GitHub Organization endpoint to retrieve the members.
Since this is GitHub related, we should create a new file,
github.ts, to host our functions and types.
github.ts file, we're going to be use axiod for communication. It's similar to axios in Node and is better than then the built-in fetch API.
Let's go ahead and bring in the import.
Calling the Organization Endpoint
axiod pulled in, let's write the function to interact with the GitHub API.
To prove this is working, we can call this function in the
index.ts file and verify that we're getting a response.
Now let's rerun the script.
Deno requests net access to "api.github.com"
- Requested by `fetch` API.
- Run again with --allow-net to bypass this prompt.
Ah! Our script is now doing something new (making network calls), so we'll need to allow that permission by using the
If everything has worked, you should see a bunch of JSON printed to the screen. Success!
Creating the Response Type
At this point, we're making the call, but we're using a pesky
any for the response, which works, but it doesn't help us with what properties we have to work with.
Looking at the response schema, it seems the main field we need is
login. So let's go ahead and create a type that includes that field.
We can rerun our code and verify that everything is still working, but now with better type safety.
Now that we have this function written, we can work to integrate it with our
So far, so good. The only change we had to make was to replace the hardcoded array of names with the call to
Not an issue, right?
Hmmm, what's up with this?
It looks like
createMessage is expecting
Pair<string>, but is receiving
To solve this problem, we'll modify
createMessage to work with
With this last change, we run the script and verify that we're getting the correct output, huzzah!
Congratulations! We now have a script that is dynamically pulling in heroes from the Justice League organization instead of always needing to see if Green Lantern is somewhere else or if another member of Flash's SpeedForce is here for the moment.
A working version of the code can be found on GitHub.