<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kirk Haines</title>
    <description>The latest articles on DEV Community by Kirk Haines (@wyhaines).</description>
    <link>https://dev.to/wyhaines</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F582566%2F1a8a4470-ec01-40fb-86c1-4e48dea64337.jpg</url>
      <title>DEV Community: Kirk Haines</title>
      <link>https://dev.to/wyhaines</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/wyhaines"/>
    <language>en</language>
    <item>
      <title>Packing Static Files Into Crystal Binaries</title>
      <dc:creator>Kirk Haines</dc:creator>
      <pubDate>Wed, 27 Oct 2021 18:10:38 +0000</pubDate>
      <link>https://dev.to/wyhaines/packing-static-files-into-crystal-binaries-4nh4</link>
      <guid>https://dev.to/wyhaines/packing-static-files-into-crystal-binaries-4nh4</guid>
      <description>&lt;p&gt;Last week, I was hanging out in the &lt;a href="https://discord.com/invite/YS7YvQy"&gt;Crystal Discord&lt;/a&gt; when what to my wondering eyes should appear? These words:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;would be cool
everything in one assembly
a single executable to do everything
actually i dont think crystal can pack images and files into the result executable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the help of Crystal macros, this very thing is quite doable. I did not realize at the time that there was an &lt;a href="https://github.com/schovi/baked_file_system"&gt;existing project&lt;/a&gt; that provides a version of this functionality, and so I wrote my own version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Baked File System
&lt;/h2&gt;

&lt;p&gt;I should mention the existing project. The &lt;code&gt;baked_file_system&lt;/code&gt; shard hasn't seen maintenance since Crystal 0.35.1. Running it under Crystal 1.2.1, all of its specs pass, but it does throw an exception (which doesn't cause spec failure). So, it may or may not work out of the box right now, but if it needs fixes, they should be very modest.&lt;/p&gt;

&lt;p&gt;The Baked File System seeks to provide a file-system-like-capability for static files that are compiled into the executable. One can treat them, more or less, like read-only files. It also automatically applies compression to the files, sacrificing some speed for space savings. It is a very neat little project, and offers a nice approach to this problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Version
&lt;/h2&gt;

&lt;p&gt;What I wrote is less ambitious. It doesn't seek, in any way, to emulate a filesystem approach to accessing the data. It simply packs the data into the executable, within structs that are held and organized in a hash-like way, for easy access. Files which are stored in the executable are accessed via the paths that were used when storing them, and there are methods made available to query/search the file store using just fragments of the path.&lt;/p&gt;

&lt;p&gt;The end result is a small, fast, lean system that can be used to pack static files into an executable, and to access that data as needed. So let's take a look at just how that works.&lt;/p&gt;

&lt;h1&gt;
  
  
  How It Works
&lt;/h1&gt;

&lt;p&gt;Crystal macros write new code at compile time. In most cases, this facility is used to generate code based on arguments passed into the macro. A standard library example of this is &lt;a href="https://crystal-lang.org/api/latest/toplevel.html#record%28name%2C%2Aproperties%29-macro"&gt;&lt;code&gt;record&lt;/code&gt;&lt;/a&gt; macro, which is used to create a new struct, complete with predefined data fields and any methods that the programmer wants to include.&lt;/p&gt;

&lt;p&gt;Crystal macros, however, also have several tools to let someone access other sources of data at compile time. One of these is the &lt;a href="https://crystal-lang.org/api/latest/Crystal/Macros.html#read_file%28filename%29%3AStringLiteral-instance-method"&gt;&lt;code&gt;read_file&lt;/code&gt;&lt;/a&gt; method.##&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://crystal-lang.org/api/latest/Crystal/Macros.html#read_file%28filename%29%3AStringLiteral-instance-method"&gt;&lt;code&gt;read_file&lt;/code&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;This macro method reads a file and returns at &lt;code&gt;StringLiteral&lt;/code&gt; with the contents of the file. At its core, this is all that one truly needs to inject the contents of a file into an executable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="n"&gt;file_contents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="n"&gt;read_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/path/to/file"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Breaking it down for you, the code inside of the &lt;code&gt;{{ }}&lt;/code&gt; is macro code, the result of which is inserted at that location, as crystal code. In this case, the macro code uses &lt;code&gt;read_file&lt;/code&gt; to read the contents of a file, and those contents are inserted &lt;em&gt;as a string&lt;/em&gt;, at that location.&lt;/p&gt;

&lt;p&gt;This is very simple, and for one or even a small handful of files, there would be little reason to reach for any sort of shard to make this easier. However, as with everything, where there is a repeated pattern that involves a lot of the same code, abstractions can make that task more pleasant to work with. That is what the &lt;code&gt;datapack&lt;/code&gt; shard is for.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://wyhaines.github.io/datapack.cr/Datapack.html#add%28path%2Cnamespace%3D%22default%22%2Cmimetype%3Dnil%29-macro"&gt;&lt;code&gt;Datapack.add&lt;/code&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;When you need to add a clearly defined set of files to the executable, you can use &lt;code&gt;Datapack.add&lt;/code&gt; for this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="no"&gt;Datapack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/path/to/file.txt"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This macro creates a line which would be something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="no"&gt;Datapack&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"default://path/to/file.txt"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Datapack&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="ss"&gt;path: &lt;/span&gt;&lt;span class="s2"&gt;"/path/to/file.txt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;data: &lt;/span&gt;&lt;span class="s2"&gt;"if this were the contents of the file, this would be the contents of the file"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;mimetype: &lt;/span&gt;&lt;span class="s2"&gt;"text/plain; charset=utf-8"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;add()&lt;/code&gt; macro makes a naive attempt to determine the mimetype of the file, but it only maps about 30 common mime types within the macro, so when in doubt, you can always specify the mimetype yourself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="no"&gt;Datapack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/path/to/file.txt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;mimetype: &lt;/span&gt;&lt;span class="s2"&gt;"text/plain; charset=utf-8"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, take note of the path that is given to the &lt;code&gt;Resource&lt;/code&gt;: &lt;code&gt;default://path/to/file.txt&lt;/code&gt;. Every resource that is added to the datapack must have a unique path, and that path can be prefixed with a namespace. The namespace is some label, followed by a colon, and if it is not specified, it will default to &lt;code&gt;default&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Namespaces can be useful to categorize assets, and are primarily implemented for programmer convenience. If they are not useful for someone's use case, they can be safely ignored, as the code will automatically ensure that a &lt;code&gt;default&lt;/code&gt; namespace is applied in that case.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://wyhaines.github.io/datapack.cr/Datapack.html#add_path%28path%2C%2Aglobs%2C%2A%2Aoptions%29-macro"&gt;&lt;code&gt;Datapack.add_path&lt;/code&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;In cases where there are many files to add, or where it is not known at the time that the code it written exactly which files need to be included into the compiled binary, there is another macro that one can use. This macro is &lt;code&gt;Datapack.add_path&lt;/code&gt;, and it does just what its name suggests, adding all of the files within a given path, that match any of the given &lt;a href="https://crystal-lang.org/api/1.2.1/File.html#match%3F%28pattern%3AString%2Cpath%3APath%7CString%29%3ABool-class-method"&gt;file glob patterns&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="no"&gt;Datapack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/path/to/files"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"**/*.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"**/*.jpg"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"**/*.gif"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will add all &lt;code&gt;png&lt;/code&gt;, &lt;code&gt;jpg&lt;/code&gt;, and &lt;code&gt;gif&lt;/code&gt; files within the &lt;code&gt;/path/to/files&lt;/code&gt; directory and all of its subdirectories to the datapack.&lt;/p&gt;

&lt;p&gt;Macro code is extremely limited with regard to what it can do. Other than the &lt;code&gt;read_file&lt;/code&gt; method which was mentioned earlier, there are no facilities within Crystal macros to interact with the filesystem in any way. However, Crystal does provide an escape-hatch. It provides a &lt;a href="https://crystal-lang.org/api/1.2.1/Crystal/Macros.html#run%28filename%2C%2Aargs%29%3AMacroId-instance-method"&gt;&lt;code&gt;run&lt;/code&gt;&lt;/a&gt; macro method. The method will &lt;em&gt;compile and execute&lt;/em&gt; the file given as an argument, and whatever is returned from that execution is returned from the &lt;code&gt;run&lt;/code&gt; call as a &lt;code&gt;MacroId&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For the purpose of packing an entire directory, there is a very small utility bundled into the shard that, when compiled, yields a simple script that walks a directory, matching any of the file pattern globs that it was given (and matching *&lt;em&gt;/&lt;/em&gt; if nothing was provided), and then returns both the path to each file, and it's MIME type, as determined by the standard Crystal MIME library. This results in a pretty robust solution for finding files to read, and for determining their MIME types. Any file with a type that still can not be determined will be labeled as &lt;code&gt;application/octet-stream&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It is easy to imagine a scenario where one has a complete web site, or some application where, for business or ease-of-distribution purposes, it would be nice if the whole thing could be packaged into a single executable file, with no other dependency on any resources within the filesystem.&lt;/p&gt;

&lt;p&gt;As an example, consider this small &lt;a href="https://kemalcr.com/"&gt;Kemal&lt;/a&gt; application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"kemal"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"datapack"&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Dpex&lt;/span&gt;
  &lt;span class="no"&gt;VERSION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0.1.0"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;Datapack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"./assets"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"**/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="ss"&gt;namespace: &lt;/span&gt;&lt;span class="s2"&gt;"assets"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s2"&gt;"/assets/:path"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Datapack&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"assets:/./assets/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt;
    &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mimetype&lt;/span&gt;
    &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Status&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;NOT_FOUND&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;Kemal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will compile all of the files which are in the &lt;code&gt;assets/&lt;/code&gt; directory into the Kemal server's executable, and any access to &lt;code&gt;/assets/&lt;/code&gt; will be served out of those compiled in files. All that is needed is the compiled executable file.&lt;/p&gt;

&lt;p&gt;If you are curious, here is how the performance looks on my desktop, running Ubuntu 20.04 under WSL2.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Document Path:          /assets/bar.html
Document Length:        69 bytes

Concurrency Level:      20
Time taken for tests:   0.442 seconds
Complete requests:      10000
Failed requests:        0
Total transferred:      1690000 bytes
HTML transferred:       690000 bytes
Requests per second:    22602.60 [#/sec] (mean)
Time per request:       0.885 [ms] (mean)
Time per request:       0.044 [ms] (mean, across all concurrent requests)
Transfer rate:          3730.31 [Kbytes/sec] received
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Searching the Datapack
&lt;/h2&gt;

&lt;p&gt;In addition to the basic Hash-like &lt;code&gt;#[]&lt;/code&gt; and &lt;code&gt;#[]=&lt;/code&gt; methods for accessing the datapack, the Datapack shard offers several convenience methods for searching the files stored in the pack. It creates a simple Path based index of all of the files, based on the individual fragments of the path. There is a set of convenience methods that can search this index in order to find files with only partial path information, or to find collections of files under a given namespace or partial path.&lt;/p&gt;

&lt;p&gt;The following methods are provided:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;#[]&lt;/code&gt; - Returns the resource at the given path.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;#find&lt;/code&gt; - Finds a single file by &lt;code&gt;Path&lt;/code&gt; or a &lt;code&gt;String&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;#find?&lt;/code&gt; - Finds a single file by &lt;code&gt;Path&lt;/code&gt; or a &lt;code&gt;String&lt;/code&gt;, returning &lt;code&gt;nil&lt;/code&gt; if not found&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;#find_all&lt;/code&gt; - Finds all files matching a given &lt;code&gt;Path&lt;/code&gt; or &lt;code&gt;String&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;#find_key&lt;/code&gt; - Finds a single key by a &lt;code&gt;Path&lt;/code&gt; or &lt;code&gt;String&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;#find_key?&lt;/code&gt; - Finds a single key by a &lt;code&gt;Path&lt;/code&gt; or &lt;code&gt;String&lt;/code&gt;, returning &lt;code&gt;nil&lt;/code&gt; if not found&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;#find_all_keys&lt;/code&gt; - Finds all keys matching a given &lt;code&gt;Path&lt;/code&gt; or &lt;code&gt;String&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Usage looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Datapack&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"assets:/./assets/bar.html"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;the_same_file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Datapack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"assets:/bar.html"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;asset_keys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Datapack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_all_keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"assets:"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;images&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Datapack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"assets:/images"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Embedding static files within an Crystal executable is a straightfoward process, using a macro and the &lt;code&gt;read_file&lt;/code&gt; macro method. If you are embedding a lot of files, or you want convenience methods for accessing and searching the files that are embedded, a shard like [datapack.cr] or the older and more filesystem-oriented &lt;a href="https://github.com/schovi/baked_file_system"&gt;baked-file-system&lt;/a&gt; is a lot more convenient than writing all of the file reading boilerplate yourself.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>crystal</category>
    </item>
    <item>
      <title>Twitch EventSub - The Direct Approach to Getting Started With It</title>
      <dc:creator>Kirk Haines</dc:creator>
      <pubDate>Wed, 02 Jun 2021 05:51:08 +0000</pubDate>
      <link>https://dev.to/wyhaines/twitch-eventsub-the-direct-approach-to-getting-started-with-it-3dcj</link>
      <guid>https://dev.to/wyhaines/twitch-eventsub-the-direct-approach-to-getting-started-with-it-3dcj</guid>
      <description>&lt;h1&gt;
  
  
  Start Here
&lt;/h1&gt;

&lt;p&gt;You want to write something to react to Twitch Events. You look at the docs, and maybe it is a little confusing. You see a table of contents that looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.therelicans.com%2Fremoteimages%2Fuploads%2Farticles%2Fviziatamr19m2wilwzyf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.therelicans.com%2Fremoteimages%2Fuploads%2Farticles%2Fviziatamr19m2wilwzyf.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Authentication&lt;/p&gt;

&lt;p&gt;Twitch API&lt;/p&gt;

&lt;p&gt;EventSub&lt;/p&gt;

&lt;p&gt;PubSub&lt;/p&gt;

&lt;p&gt;If you click around a little, you might get more confused. There are a lot of details, and there seems to be more than one way to do things, at least in some cases, and it is easy to lose track of the thread of things amidst all of the information.&lt;/p&gt;

&lt;p&gt;You might look at PubSub, and you might think, "Wow! This seems very simple and straightforward!"&lt;/p&gt;

&lt;p&gt;You would be right. It is. Then you might discover that it only provides access to a small percentage of the Twitch events, and things that you really want, like &lt;em&gt;channel.follow&lt;/em&gt; notifications, are not available.&lt;/p&gt;

&lt;p&gt;Eventually, you look more at EventSub. This is the future. This is what everyone should be using:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.therelicans.com%2Fremoteimages%2Fuploads%2Farticles%2F6zlhgyi6o0jq0mxt8ydm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.therelicans.com%2Fremoteimages%2Fuploads%2Farticles%2F6zlhgyi6o0jq0mxt8ydm.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;"Awesome!" you think. "Let's dive in and....wait, how do I use this?"&lt;/p&gt;

&lt;p&gt;To use it, you have to subscribe to events. And to subscribe to events, you have to have authorization. And to be authorized...there are no links there, but in the left menu there is an &lt;em&gt;Authentication&lt;/em&gt; link, so you click there, and...&lt;/p&gt;

&lt;p&gt;OK, more steps. Registering the app. Then getting a token. Only there are 5 different kinds. Which one is needed? OK, and how do I get that? And how do I use it once I have it?&lt;/p&gt;

&lt;p&gt;You get the picture. The details are all there, in the documentation, but it is a maze of twisty little passages, and you may spend a lot of time flipping from one page to another to piece it all together.&lt;/p&gt;

&lt;p&gt;Fear not. I've done the flipping. I've got your back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 -- You need to register your app
&lt;/h2&gt;

&lt;p&gt;First things first -- app registration. In order for your app to interact with eventsub, Twitch wants to know about it as a unique entity. So, you need to register it.&lt;/p&gt;

&lt;p&gt;Go to &lt;a href="https://dev.twitch.tv/console" rel="noopener noreferrer"&gt;https://dev.twitch.tv/console&lt;/a&gt;. On the right of the page, there should be a section labeled &lt;em&gt;Applications&lt;/em&gt;. Click on the &lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.therelicans.com%2Fremoteimages%2Fuploads%2Farticles%2F19wipr547908xlyz5xbh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.therelicans.com%2Fremoteimages%2Fuploads%2Farticles%2F19wipr547908xlyz5xbh.png" alt="Register Your Application"&gt;&lt;/a&gt; button.&lt;/p&gt;

&lt;p&gt;In the form that is on the next page, you have to provide a few elements of information.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.therelicans.com%2Fremoteimages%2Fuploads%2Farticles%2F9z5mh56ughnbn5iucs0i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.therelicans.com%2Fremoteimages%2Fuploads%2Farticles%2F9z5mh56ughnbn5iucs0i.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Give your app a name.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.therelicans.com%2Fremoteimages%2Fuploads%2Farticles%2Fkhtrtdmx6amnuu4178oq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.therelicans.com%2Fremoteimages%2Fuploads%2Farticles%2Fkhtrtdmx6amnuu4178oq.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Unless you know with certainty what this is or will be, just put &lt;code&gt;https://localhost&lt;/code&gt; in, and press the &lt;em&gt;Add&lt;/em&gt; button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.therelicans.com%2Fremoteimages%2Fuploads%2Farticles%2Feus40lenybem427qwctx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.therelicans.com%2Fremoteimages%2Fuploads%2Farticles%2Feus40lenybem427qwctx.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, select an appropriate category for your app, and then press the &lt;em&gt;Create&lt;/em&gt; button.&lt;/p&gt;

&lt;p&gt;You will be taken to the Apps Console, where you will see something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.therelicans.com%2Fremoteimages%2Fuploads%2Farticles%2Falyja1onc0c2t28qdg4o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.therelicans.com%2Fremoteimages%2Fuploads%2Farticles%2Falyja1onc0c2t28qdg4o.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The next step is to click the &lt;em&gt;Manage&lt;/em&gt; button on your newly created app. You will be taken back to a page that looks just like the one where you created the app, except that it has a few extra bits of information at the bottom:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.therelicans.com%2Fremoteimages%2Fuploads%2Farticles%2Fan9trucskqpgfj8xcdtv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.therelicans.com%2Fremoteimages%2Fuploads%2Farticles%2Fan9trucskqpgfj8xcdtv.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Client ID is public information. There is no need to hide that. The application secret, however, is, well, secret. It will be generated when you press the &lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.therelicans.com%2Fremoteimages%2Fuploads%2Farticles%2Fl0nc65vc6yr9wg94a1pm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.therelicans.com%2Fremoteimages%2Fuploads%2Farticles%2Fl0nc65vc6yr9wg94a1pm.png" alt="image"&gt;&lt;/a&gt; button. Take note of the string of characters that are revealed when you press that button, and save it somewhere else for later use. You can not see it again once you leave the page, so if you lose the secret, you will have to regenerate a new one, which expires the old one.&lt;/p&gt;

&lt;p&gt;See? This is easy so far.&lt;/p&gt;

&lt;p&gt;The next thing that you need to do is to generate an access token for your application, using the &lt;em&gt;Client-ID&lt;/em&gt; and &lt;em&gt;Client-Secret&lt;/em&gt; that you have just generated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 -- Generate your access token
&lt;/h2&gt;

&lt;p&gt;The next step is to generate your access token. There are five different types of access token, but the one that is needed for EventSub is the &lt;a href="https://dev.twitch.tv/docs/authentication/getting-tokens-oauth/#oauth-client-credentials-flow" rel="noopener noreferrer"&gt;OAuth Client Credentials Flow&lt;/a&gt; token type.&lt;/p&gt;

&lt;p&gt;This type of token is an Application Access Token, intended only for server-to-server API requests, which is exactly what is needed for EventSub activities.&lt;/p&gt;

&lt;p&gt;To get your very own shiny, new Application Access Token, you need to make a POST request to the Twitch API. The documentation detailing what is needed is in the link above, but it has the potential to be a little bit confusing.&lt;/p&gt;

&lt;p&gt;HTTP &lt;em&gt;GET&lt;/em&gt; requests pass extra parameters within the query string of the URL.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;GET /foo?param1=abc&amp;amp;param2=123&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;HTTP &lt;em&gt;POST&lt;/em&gt; requests typically pass parameters within the body of the HTTP request. The examples show a &lt;em&gt;POST&lt;/em&gt; being done with the data in the URL as a Query String.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.therelicans.com%2Fremoteimages%2Fuploads%2Farticles%2Fq7gmbibds4t9s4g4dzfy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.therelicans.com%2Fremoteimages%2Fuploads%2Farticles%2Fq7gmbibds4t9s4g4dzfy.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While this is not illegal per the HTTP spec, it is not typical, nor is it required when working with the Twitch API.&lt;/p&gt;

&lt;p&gt;If you want to generate an access key manually, you can do this using &lt;code&gt;curl&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nv"&gt;client_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$TWITCH_CLIENT_ID&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nv"&gt;client_secret&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$TWITCH_CLIENT_SECRET&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nv"&gt;grant_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;client_credentials &lt;span class="se"&gt;\ &lt;/span&gt;
     https://id.twitch.tv/oauth2/token
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your &lt;em&gt;Client-ID&lt;/em&gt; and your &lt;em&gt;Client-Secret&lt;/em&gt; are stored in a couple of environment variables, &lt;em&gt;TWITCH_CLIENT_ID&lt;/em&gt; and &lt;em&gt;TWITCH_CLIENT_SECRET&lt;/em&gt;, the above should work from most unix-like command lines. &lt;/p&gt;

&lt;p&gt;What is returned is one of two things. If either your &lt;em&gt;Client-ID&lt;/em&gt; is invalid, there will be an error like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"invalid client"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the &lt;em&gt;Client-ID&lt;/em&gt; is valid, but the &lt;em&gt;Client-Secret&lt;/em&gt; is invalid, the error will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"invalid client secret"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If both are valid, the result will be returned in JSON something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"access_token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"q3b5n90ua7du0mgpwl149ge2yf90r0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"expires_in"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;4776914&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"token_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"bearer"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The value for the &lt;code&gt;access_token&lt;/code&gt; key is your golden ticket. It is what permits you to access the rest of the EventSub API.&lt;/p&gt;

&lt;p&gt;If you want or need your software to be able to generate an access key at will, though, you will need to issue the request and receive the response programmatically. The details of this may vary considerably depending on your programming language, but maybe I can help with a few examples:&lt;/p&gt;

&lt;h3&gt;
  
  
  Ruby
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"uri"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"net/http"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"json"&lt;/span&gt;

&lt;span class="n"&gt;uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"https://id.twitch.tv/oauth2/token"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
             &lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
             &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"client_id"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="s2"&gt;"client_secret"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="s2"&gt;"grant_type"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"client_credentials"&lt;/span&gt;&lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;to_json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Content-Type"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;access_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="s2"&gt;"access_token"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Javascript
&lt;/h3&gt;

&lt;p&gt;The Javascript example assumes the use of &lt;em&gt;Fetch&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://id.twitch.tv/oauth2/token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;client_secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;grant_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;client_credentials&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;access_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;
&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;access_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;access_token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Crystal
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"http/client"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"json"&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="ss"&gt;url: &lt;/span&gt;&lt;span class="s2"&gt;"https://id.twitch.tv/oauth2/token"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;headers: &lt;/span&gt;&lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"application/json"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"client_id"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"client_secret"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"grant_type"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"client_credentials"&lt;/span&gt;&lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;to_json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;access_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="s2"&gt;"access_token"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;as_s&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Bash
&lt;/h3&gt;

&lt;p&gt;The Bash example assumes that the &lt;a href="https://stedolan.github.io/jq/" rel="noopener noreferrer"&gt;jq&lt;/a&gt; utility is installed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;DATA&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;curl &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nv"&gt;client_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;CLIENT_ID &lt;span class="se"&gt;\&lt;/span&gt;
           &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nv"&gt;client_secret&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;CLIENT_SECRET &lt;span class="se"&gt;\&lt;/span&gt;
           &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nv"&gt;grant_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;client_credentials &lt;span class="se"&gt;\&lt;/span&gt;
           &lt;span class="nt"&gt;-s&lt;/span&gt; https://id.twitch.tv/oauth2/token&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;span class="nv"&gt;ACCESS_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$DATA&lt;/span&gt; | jq .access_token
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you have a valid &lt;em&gt;access token&lt;/em&gt;, the world is your oyster. The rest of the Twitch EventSub API is accessible.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Quick Note About the EventSub API URL
&lt;/h2&gt;

&lt;p&gt;The sections that follow provide examples for how to perform each of the EventSub management actions, and an astute reader will note that the URL for each of the sections is the same. All EventSub API actions operate through the same API URL:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://api.twitch.tv/helix/eventsub/subscriptions&lt;/code&gt;'&lt;/p&gt;

&lt;p&gt;What differentiates the different types of actions that can be performed with EventSub requests are the HTTP verbs that are used to perform the request and the payload that accompanies it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Listing Subscriptions
&lt;/h2&gt;

&lt;p&gt;The EventSub API provides a mechanism to see what subscriptions a client currently has and their status. This is important, because there is a limited number of subscriptions allowed per client (10000), and even failed subscription requests count against that limit. This makes it important to monitor all current subscriptions so that failed or unneeded subscriptions can be deleted.&lt;/p&gt;

&lt;p&gt;To access a list of subscriptions, a &lt;em&gt;GET&lt;/em&gt; request must be issues to &lt;em&gt;&lt;a href="https://api.twitch.tv/helix/eventsub/subscriptions" rel="noopener noreferrer"&gt;https://api.twitch.tv/helix/eventsub/subscriptions&lt;/a&gt;&lt;/em&gt;. This request should provide &lt;em&gt;Client-ID&lt;/em&gt; and &lt;em&gt;Authorization&lt;/em&gt; parameters, where the value of the &lt;em&gt;Authorization&lt;/em&gt; parameter is the access token generated earlier, with &lt;code&gt;Bearer&lt;/code&gt; prepended to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Authorization: Bearer deadbeefdeadbeef
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With a valid access token, the response will be a JSON payload with a &lt;code&gt;data&lt;/code&gt; field containing a list of subscriptions, along with some fields showing the limit on the number of subscriptions, as well as how many total subscriptions the client has.&lt;/p&gt;

&lt;p&gt;A full specification for this API request can be found at &lt;a href="https://dev.twitch.tv/docs/api/reference#get-eventsub-subscriptions" rel="noopener noreferrer"&gt;https://dev.twitch.tv/docs/api/reference#get-eventsub-subscriptions&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a Subscription
&lt;/h2&gt;

&lt;p&gt;This is the most complex operations when dealing with EventSub, as subscription creation also involves a verification step that allows Twitch to validate that the callback that was given in the subscription request is owned by the client that requested it, as well as a signature validation to allow the client to verify that the Twitch verification request is itself valid.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 1 -- Request a subscription
&lt;/h4&gt;

&lt;p&gt;A subscription request is initiated by sending a &lt;em&gt;POST&lt;/em&gt; request to &lt;em&gt;&lt;a href="https://api.twitch.tv/helix/eventsub/subscriptions" rel="noopener noreferrer"&gt;https://api.twitch.tv/helix/eventsub/subscriptions&lt;/a&gt;&lt;/em&gt; with the following HTTP Headers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Client-ID: CLIENT_ID
Authorization: Bearer ACCESS_TOKEN
Content-Type: application/json
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Within the body of the request, a JSON payload with four keys, &lt;code&gt;version&lt;/code&gt;, &lt;code&gt;type&lt;/code&gt;, &lt;code&gt;condition&lt;/code&gt;, and &lt;code&gt;transport&lt;/code&gt; is expected. Each should have a value as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;version&lt;/strong&gt; : Currently, this is always &lt;code&gt;1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;type&lt;/strong&gt; : This is the &lt;a href="https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types" rel="noopener noreferrer"&gt;name of the event&lt;/a&gt; to subscribe to.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;condition&lt;/strong&gt; : This is an object which itself will have a single key, &lt;code&gt;broadcaster_user_id&lt;/code&gt;, which contains the numeric user id of the account that is requesting the subscription.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;transport&lt;/strong&gt; : This is an object with three keys, &lt;code&gt;method&lt;/code&gt;, &lt;code&gt;callback&lt;/code&gt;, and &lt;code&gt;secret&lt;/code&gt;, which are used to specify the transport mechanism for Twitch to send event information. Currently only &lt;code&gt;webhook&lt;/code&gt; is supported for the &lt;code&gt;method&lt;/code&gt; key, though the documentation alludes to plans to support others in the future. The &lt;code&gt;callback&lt;/code&gt; will be the URL that Twitch will contact when the subscribed-to-event occurs, and the &lt;code&gt;secret&lt;/code&gt; should be a 10 to 100 character secret value unique to this subscription.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The secret will be used to validate the subsequent subscription request verification, so your code must remember what it sent as the secret for this subscription.&lt;/p&gt;

&lt;p&gt;The whole package to issue a request for a &lt;em&gt;channel.follow&lt;/em&gt; subscription will look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;version:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;type:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"channel.follow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"broadcaster_user_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"12826"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"transport"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"webhook"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"callback"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://example.com/webhooks/callback"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"secret"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"abcdefghij0123456789"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;  
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 2 -- Receive a response indicating request status
&lt;/h4&gt;

&lt;p&gt;In response to the subscription request, Twitch will send a JSON payload as a response. If the request was successfully received, the response from Twitch will be similar to that described above when listing subscriptions, except that the subscription contained in the &lt;em&gt;data&lt;/em&gt; array will have a status of &lt;em&gt;webhook_callback_verification_pending&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"webhook_callback_verification_pending"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This indicates that Twitch has received and will be verifying the subscription request.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 3 -- Receive verification
&lt;/h4&gt;

&lt;p&gt;Twitch must verify that the callback provided in the subscription request belongs to the caller. To that end, it will contact the callback URL in order to initiate a verification exchange.&lt;/p&gt;

&lt;p&gt;It will make a &lt;em&gt;POST&lt;/em&gt; request to the callback URL. The handler for the callback URL must be able to recognize a Twitch verification request and respond appropriately. Twitch sets a number of custom HTTP headers on the request, several of which are particularly important:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Twitch-Eventsub-Message-Id&lt;/strong&gt; : This is a UUID representing the unique ID of this specific message. This will be used in &lt;em&gt;step 4&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Twitch-Eventsub-Message-Timestamp&lt;/strong&gt; : The timestamp is also used in &lt;em&gt;step 4&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Twitch-Eventsub-Message-Type&lt;/strong&gt; : This is the message type. For a verification attempt, this will be set to &lt;code&gt;webhook_callback_verification&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Twitch-Eventsub-Message-Signature&lt;/strong&gt; : This is a &lt;em&gt;HMAC-SHA256&lt;/em&gt; message signature in the format of &lt;code&gt;sha256=4471d611ed1f44cf2fe1d7a462fc62&lt;/code&gt;. This is used in &lt;em&gt;step 4&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Twitch-Eventsub-Subscription-Type&lt;/strong&gt; : This is the subscription type that is being verified. From the example above, this would be &lt;code&gt;channel.follow&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the body of the request will be a JSON payload that contains another copy of the subscription, in the same format that has already been discussed, along with a &lt;em&gt;challenge&lt;/em&gt; key, and a value in the form of a random string of letters and digits separated into clusters by dashes. The &lt;em&gt;challenge&lt;/em&gt; will be used after the request is validated, in &lt;em&gt;step 5&lt;/em&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 4 -- Validate the request's &lt;em&gt;Message-Signature&lt;/em&gt;
&lt;/h4&gt;

&lt;p&gt;The &lt;em&gt;Twitch-Eventsub-Message-Signature&lt;/em&gt; is calculated with &lt;em&gt;HMAC-SHA256&lt;/em&gt; using the secret that was provided to Twitch in the original subscription request. It is a concantenation of the value of the &lt;em&gt;Twitch-Eventsub-Message-Id&lt;/em&gt; and the &lt;em&gt;Twitch-Eventsub-Message-Timestamp&lt;/em&gt; headers with the message body, signed using &lt;em&gt;HMAC-SHA256&lt;/em&gt; with the aforementioned secret.&lt;/p&gt;

&lt;p&gt;If the calculated signature does not match the signature that was provided in the &lt;em&gt;Twitch-Eventsub-Message-Signature&lt;/em&gt; header, return a 403 status. If it does match, continue to &lt;em&gt;step 5&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Your code will probably look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="n"&gt;calculated_signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;OpenSSL&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HMAC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="no"&gt;OpenSSL&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Algorithm&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SHA256&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Twitch-Eventsub-Message-Id"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Twitch-Eventsub-Message-Timestamp"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gets_to_end&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Twitch-Eventsub-Message-Signature"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;calculated_signature&lt;/span&gt;
  &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respond_with_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
  &lt;span class="c1"&gt;# Yay! The Signature was verified. Continue with processing.&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 5 -- Respond to the verification request with the &lt;em&gt;challenge&lt;/em&gt;
&lt;/h4&gt;

&lt;p&gt;At this point, your code will have validated Twitch's request for verification. The only thing that is left to do is to respond to the validation request.&lt;/p&gt;

&lt;p&gt;As mentioned previously, the JSON payload in the body of the request will have contained a key &lt;em&gt;challenge&lt;/em&gt;. The value for this key must be returned in a status code 200 response to the Twitch request, with nothing added or changed.&lt;/p&gt;

&lt;p&gt;A sample of how this might look is as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request_body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gets_to_end&lt;/span&gt;
&lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;challenge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"challenge"&lt;/span&gt;&lt;span class="p"&gt;]?&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;challenge&lt;/span&gt;
  &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
  &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt; &lt;span class="n"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_slice&lt;/span&gt;
  &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 6 -- There is no step 6
&lt;/h4&gt;

&lt;p&gt;At this point the subscription is active.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deleting a Subscription
&lt;/h2&gt;

&lt;p&gt;At some point, you will want to delete a subscription, either because you no longer need the information, or because a subscription request failed, and you need to clear it out.&lt;/p&gt;

&lt;p&gt;Deleting subscriptions is a straight forward process, fortunately.&lt;/p&gt;

&lt;p&gt;Every subscription has a UUID that identifies it. This ID can be retrieved from the JSON response that is returned when subscriptions are listed. To delete a subscription, a &lt;em&gt;DELETE&lt;/em&gt; request is sent to the &lt;em&gt;&lt;a href="https://api.twitch.tv/helix/eventsub/subscriptions" rel="noopener noreferrer"&gt;https://api.twitch.tv/helix/eventsub/subscriptions&lt;/a&gt;&lt;/em&gt; URL, with &lt;em&gt;Client-ID&lt;/em&gt; and &lt;em&gt;Authorization&lt;/em&gt; headers, just as for listing subscriptions. &lt;/p&gt;

&lt;h2&gt;
  
  
  Handling Notifications
&lt;/h2&gt;

&lt;p&gt;When an event occurs for one of the active subscriptions, Twitch will send a &lt;em&gt;POST&lt;/em&gt; request to the callback URL with the details. The headers will be the same as was discussed above for subscription verification, except that the &lt;em&gt;Twitch-Eventsub-Message-Type&lt;/em&gt; header will have a value of `notification.&lt;/p&gt;

&lt;p&gt;The JSON payload for the request will contain an object with two top-level keys, &lt;code&gt;subscription&lt;/code&gt;, and &lt;code&gt;event&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The value for the &lt;code&gt;subscription&lt;/code&gt; key will contain a copy of the subscription that generated the event.&lt;/p&gt;

&lt;p&gt;The value for the &lt;code&gt;event&lt;/code&gt; key will contain an object that describes the event details. The precise values that will be available in this object depend on the event type. Please &lt;a href="https://dev.twitch.tv/docs/eventsub/eventsub-reference" rel="noopener noreferrer"&gt;refer to the Twitch documentation&lt;/a&gt; in order to figure out what to expect.&lt;/p&gt;

&lt;p&gt;It is expected that the notification will be verified in exactly the same manner that the subscription request was verified, by checking the &lt;em&gt;HMAC2SHA256&lt;/em&gt; signature of the request before trusting it. If it is validated, it is expected that a 200 response will be sent back to Twitch to confirm that the event was received. If validation fails, it is expected that a 403 response (or other appropriate 4xx response) is sent to Twitch to indicate the validation failure.&lt;/p&gt;

&lt;p&gt;If Twitch isn't sure that the event was received (such as a case where neither a 2xx nor a 4xx response are received in response to an event), Twitch may resend the event, so one's event handler must be able to cope if an event that was already received is received a second time.&lt;/p&gt;

&lt;h2&gt;
  
  
  All Of The Details Are In The API Docs
&lt;/h2&gt;

&lt;p&gt;The Twitch API documentation contains all of the above details, and while they are not presented in a linear fashion that is easy to implement-your-own-code from, they are all present in full details if one hunts enough.&lt;/p&gt;

&lt;p&gt;Please use this as a guide to get yourself going, and then refer back to the Twitch documentation for full details, as in many places some of the details have been elided in order to keep this guide as direct and simple as possible.&lt;/p&gt;




&lt;p&gt;I stream on Twitch for The Relicans. &lt;a href="https://www.twitch.tv/wyhaines" rel="noopener noreferrer"&gt;Stop by and follow me at https://www.twitch.tv/wyhaines&lt;/a&gt;, and feel free to drop in any time. In addition to whatever I happen to be working on that day, I'm always happy to field questions or to talk about anything that I may have written.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>crystal</category>
    </item>
    <item>
      <title>A Not-So-Illustrated Guide to Translating Code From Ruby To Crystal</title>
      <dc:creator>Kirk Haines</dc:creator>
      <pubDate>Tue, 20 Apr 2021 13:26:53 +0000</pubDate>
      <link>https://dev.to/wyhaines/a-not-so-illustrated-guide-to-translating-code-from-ruby-to-crystal-3ke</link>
      <guid>https://dev.to/wyhaines/a-not-so-illustrated-guide-to-translating-code-from-ruby-to-crystal-3ke</guid>
      <description>&lt;h1&gt;
  
  
  The Spark
&lt;/h1&gt;

&lt;p&gt;Once upon a time, about three years ago, I had a great idea; I wanted to implement an auto-deployment infrastructure across a bunch of servers that I was running. When a commit was pushed into GitLab/GitHub, if the branch that received the commit was deployed on one of the servers that I was managing, the repository on that server should pull the changes, and then optionally there would be a post-update hook that could be invoked to complete the local deployment.&lt;/p&gt;

&lt;p&gt;The deploy hooks for both GitLab and GitHub attach the repository URL to the payload, so what I decided that I needed was a simple way to drop a database onto a given system that could track the git repositories on that system. Ideally, it would be something fast, and something simple enough that it did not require any real ongoing management or resources.&lt;/p&gt;

&lt;p&gt;Enter &lt;a href="https://github.com/wyhaines/git-index"&gt;git-index&lt;/a&gt;. This is a simple &lt;a href="https://www.ruby-lang.org/"&gt;Ruby&lt;/a&gt; app that maintains a local &lt;a href="https://www.sqlite.org/index.html"&gt;SQLite&lt;/a&gt; database of git repositories, providing a command-line interface to add, list, query, or remove entries. &lt;/p&gt;

&lt;p&gt;One of the things that I like about &lt;a href="https://crystal-lang.org/"&gt;Crystal&lt;/a&gt; is that as a compiled language, one can build single binaries for utilities without depending on having the entire installed language at one's disposal. I figured that it might be an interesting exercise to take an hour or two to translate git-index from Ruby to Crystal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Typing Empty Arrays
&lt;/h2&gt;

&lt;p&gt;Crystal has a Ruby-inspired syntax, and on a small scale, a lot of Ruby code is also valid Crystal, but there are some notable differences.&lt;/p&gt;

&lt;p&gt;One of the first things that one will encounter when porting Ruby to Crystal is how typing changes the way empty arrays and hashes are declared. In Ruby, one does a simple assignment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;leftover_argv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will result in an error in Crystal, however:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GjbLb__O--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.therelicans.com/remoteimages/uploads/articles/h8uc8nji6w9x8hw4j7a7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GjbLb__O--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.therelicans.com/remoteimages/uploads/articles/h8uc8nji6w9x8hw4j7a7.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fortunately, the exception is clear about the problem, and the solution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="n"&gt;leftover_argv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Instance Variables
&lt;/h2&gt;

&lt;p&gt;The next major gotcha involves &lt;a href="https://crystal-lang.org/reference/syntax_and_semantics/methods_and_instance_variables.html"&gt;instance variables&lt;/a&gt;. In the original code, it was written to use class methods and to declare class instance variables.  In hindsight, I decided that was a poor choice. So, in this port, I decided to make everything work from an instance of &lt;code&gt;GitIndex&lt;/code&gt; instead of operating via class methods on &lt;code&gt;GitIndex&lt;/code&gt; itself.&lt;/p&gt;

&lt;p&gt;The implementation of this change is basically identical to how one would do it in Ruby, so I won't go into details there, but there is an important detail to consider in regard to instance variables.&lt;/p&gt;

&lt;p&gt;In Ruby, one can declare an instance variable at any point in the code, and it will work with no problems. Ruby is very dynamic in that regard. With Crystal, however, instance variables must be declared at the class level and must be initialized directly either at the class level or in the &lt;code&gt;#initialize&lt;/code&gt; constructor for the class.&lt;/p&gt;

&lt;p&gt;Failing to do so may reward one with the following type of error:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CUpbW4jA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.therelicans.com/remoteimages/uploads/articles/tfe6mt6mqd6ddp3eblwv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CUpbW4jA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.therelicans.com/remoteimages/uploads/articles/tfe6mt6mqd6ddp3eblwv.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To do indirect initialization of an instance variable without earning the error message above, the instance variable has to be declared at the class level with a nilable type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Foo&lt;/span&gt;
  &lt;span class="vi"&gt;@bar&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;     &lt;span class="c1"&gt;# This is nillable.&lt;/span&gt;
  &lt;span class="vi"&gt;@qux&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="c1"&gt;# This has a default but is not nilable.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the Ruby version of the code, the following was a fine way to initialize an instance variable for the first time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;
  &lt;span class="vi"&gt;@config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Config&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;parse_command_line&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Crystal, that initialization must occur earlier.&lt;/p&gt;

&lt;p&gt;There are a few different options. One is to specify the typing information of the instance variable at the class level:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="kp"&gt;struct&lt;/span&gt; &lt;span class="no"&gt;GitIndex&lt;/span&gt;
  &lt;span class="vi"&gt;@config&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;Hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Bool&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="no"&gt;Symbol&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse_command_line&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That works great and has the advantage that the instance variables that exist in the object definition are all clearly visible. And because the initialization can occur at the class level, there isn't even a need to provide an &lt;code&gt;#initialize&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;There is an alternative, where the initialization is done in the argument specification of the &lt;code&gt;#initialize&lt;/code&gt; method. The actual initialization code only differs from the code above by where it is placed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@config&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;Hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Bool&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="no"&gt;Symbol&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse_command_line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach combines the argument specification for the constructor with assignment to the instance variable. I like this approach because it allows the code to have a default configuration handling path, but it also allows someone to circumvent that and to provide a &lt;code&gt;Config&lt;/code&gt; instance themselves just by changing the location of the initialization.&lt;/p&gt;

&lt;h2&gt;
  
  
  Database Access
&lt;/h2&gt;

&lt;p&gt;The only other significant stumbling block to porting this utility to Crystal was porting the code that interacts with the database.&lt;/p&gt;

&lt;p&gt;Ruby has a variety of different gems that can be used for database access, with varying levels of abstraction from &lt;a href="https://www.ruby-toolbox.com/categories/SQL_Database_Adapters"&gt;gems that offer direct low level&lt;/a&gt; access to a database, to gems which offer a high level ORM, like &lt;a href="https://guides.rubyonrails.org/active_record_basics.html"&gt;ActiveRecord&lt;/a&gt; or &lt;a href="https://github.com/jeremyevans/sequel"&gt;Sequel&lt;/a&gt;, and which typically implement their own mid level abstraction layer.&lt;/p&gt;

&lt;p&gt;In the original code, I used the SQLite3 gem directly, which offers a simple API. After creating an instance of the class, one can use &lt;code&gt;#execute&lt;/code&gt; to run SQL and to perform queries in general.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://crystal-lang.org/reference/database/index.html"&gt;Crystal has a standard database interface API&lt;/a&gt; as part of the language specification. Different database drivers can implement their own backend capabilities, but they are all accessed through that common database interface API. Unlike the SQLite3 gem, where everything can be done through &lt;code&gt;#execute&lt;/code&gt;, with the Crystal database API, queries are executed via &lt;code&gt;#query&lt;/code&gt;, while other SQL is executed via &lt;code&gt;#execute&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Also, because of the very dynamic nature of Ruby, the return set of a database query can be iterated over simply as a set of arrays. Because Crystal is typed, however, and Arrays have to be typed, there is a little more work that has to be done on the Crystal side. So, as an example, this code in the Ruby version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;list_records&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"hash,path,url"&lt;/span&gt;
  &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"SELECT hash, path, url FROM repositories"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;','&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looks like this in the Crystal version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;list_records&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"hash,path,url"&lt;/span&gt;
  &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"SELECT hash, path, url FROM repositories"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;rs&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;rs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;rs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;rs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;rs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)].&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;","&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Crystal is a little more verbose for two reasons. First, a query returns a result set that must itself be iterated over to access each of the rows in the result set, and second, each field must be read with typing information provided. Similar changes have to be made in each of the methods that queried data from the database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build It!
&lt;/h2&gt;

&lt;p&gt;You can access the source code, or download a prebuilt Linux x86_64 binary via the GitHub project:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/wyhaines/git-index.cr"&gt;https://github.com/wyhaines/git-index.cr&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you have Crystal installed locally, you can build it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--G1-tpiGZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.therelicans.com/remoteimages/uploads/articles/q74tqpe0j7con1t38cli.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--G1-tpiGZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.therelicans.com/remoteimages/uploads/articles/q74tqpe0j7con1t38cli.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Profit!
&lt;/h2&gt;

&lt;p&gt;While the runtime resource usage of a CLI tool like this isn't super important, it is interesting to illustrate the difference between the Ruby version and the nearly identical Crystal version.&lt;/p&gt;

&lt;p&gt;On a real server with 53 indexed repositories, the Ruby version's &lt;code&gt;time&lt;/code&gt; information when executing &lt;code&gt;git-index -l&lt;/code&gt; is:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wSdz1rxa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.therelicans.com/remoteimages/uploads/articles/tkcdqhfv8xwbjkxgfsp7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wSdz1rxa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.therelicans.com/remoteimages/uploads/articles/tkcdqhfv8xwbjkxgfsp7.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It takes 0.15 seconds to run.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;time -v&lt;/code&gt; can be used to capture memory usage while running, as well:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TdGHC1hc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.therelicans.com/remoteimages/uploads/articles/pap3h1pd1y94grxd7sht.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TdGHC1hc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.therelicans.com/remoteimages/uploads/articles/pap3h1pd1y94grxd7sht.png" alt="image"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;And while running, it requires 14.36M of RAM.&lt;/p&gt;

&lt;p&gt;The Crystal version, on the same machine, with the same data:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YS1BLtq_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.therelicans.com/remoteimages/uploads/articles/rrecrfq5ksi1luqt6jw9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YS1BLtq_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.therelicans.com/remoteimages/uploads/articles/rrecrfq5ksi1luqt6jw9.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It runs in 0.006 seconds - about 25x faster - and it also only uses 3.98M of RAM.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wsOBLP8B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.therelicans.com/remoteimages/uploads/articles/lrzaxph95c9nf3mbn8jo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wsOBLP8B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.therelicans.com/remoteimages/uploads/articles/lrzaxph95c9nf3mbn8jo.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Crystal is a great language to port your Ruby CLI tools into!&lt;/p&gt;




&lt;p&gt;I stream on Twitch for The Relicans. &lt;a href="https://www.twitch.tv/wyhaines"&gt;Stop by and follow me at https://www.twitch.tv/wyhaines&lt;/a&gt;, and feel free to drop in any time. In addition to whatever I happen to be working on that day, I'm always happy to field questions or to talk about anything that I may have written.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>crystal</category>
      <category>rewrite</category>
    </item>
  </channel>
</rss>
