<?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: Clay Murray</title>
    <description>The latest articles on DEV Community by Clay Murray (@powerc9000).</description>
    <link>https://dev.to/powerc9000</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%2F98815%2F704ffa56-bcf5-4450-ae6f-05c873b23fa5.jpg</url>
      <title>DEV Community: Clay Murray</title>
      <link>https://dev.to/powerc9000</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/powerc9000"/>
    <language>en</language>
    <item>
      <title>The Ultimate Asset Pipeline.</title>
      <dc:creator>Clay Murray</dc:creator>
      <pubDate>Mon, 07 Sep 2020 22:50:42 +0000</pubDate>
      <link>https://dev.to/powerc9000/the-ultimate-asset-pipeline-5c3m</link>
      <guid>https://dev.to/powerc9000/the-ultimate-asset-pipeline-5c3m</guid>
      <description>&lt;p&gt;I have two &lt;a href="https://dev.to/powerc9000/asset-pipeline-in-my-game-2ep6"&gt;previous&lt;/a&gt; &lt;a href="https://dev.to/powerc9000/asset-pipeline-updates-1cnb"&gt;posts&lt;/a&gt; about my adventures in creating a pipeline to get my Raw PSD files into a PNG texture atlas. &lt;/p&gt;

&lt;p&gt;Well it turned into an entire GUI desktop application, &lt;a href="https://powerc9000.itch.io/cute-asset-pipeline"&gt;Cute Exporter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With this newest way of exporting assets I do have it all! Every step in the pipeline can be controlled by the Exporter (and therefore whatever code I write). It makes adding in expanded metadata easy, and works how I want it to. Maybe you can find it useful too.&lt;/p&gt;

&lt;p&gt;The exporter can do everything I was doing before through several scripts and programming languages by itself. No need to install any other programs.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read PSD or PNG or Aseprite files.&lt;/li&gt;
&lt;li&gt;Edit and export meta data about each asset file and Layer in that file.&lt;/li&gt;
&lt;li&gt;Export each layer in the Asset to a PNG and PNG texture atlas.&lt;/li&gt;
&lt;li&gt;Export all metadata to a json file to get info about each entry in the texture atlas and tags and other metadata.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Seriously, You don't need to own Photoshop or Aseprite to export any of those files! It also has a CLI (which I need to document more) included. You can automate your workflow with it pretty easily as well.&lt;/p&gt;

&lt;p&gt;The tool is written in a programming language called &lt;a href="https://odin-lang.org/"&gt;Odin&lt;/a&gt;. It's a lower level programming language, with a small but engaged community. It's a bit of an mix between go and C. If you ever wanted to do some lower level programming it might be one to try out. &lt;a href="https://discord.gg/55Vz9MP"&gt;There is a community discord&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the graphics and windowing stuff I use a library called &lt;a href="https://www.raylib.com/"&gt;Raylib&lt;/a&gt;. Super beginner friendly and easy to start with for games. But it is in C. However, there are ports of it to many other languages so check those out too. Odin has a great foreign system so I compile the Raylib code as a static library and link it with my odin code. (I should clean up my Odin code for this and make the repo public).&lt;/p&gt;

&lt;p&gt;I'm still using sqlite because I think it rules. Similar procedure to Raylib. I compile the sqlite as an object file and link it in Odin. You can check out that code at the &lt;a href="https://github.com/powerc9000/odin_sqlite"&gt;repo I made for it&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Maybe you don't care about games. Well it still can be useful for you in the web as well. &lt;/p&gt;

&lt;p&gt;It can be useful to combine your little assets into one image. Then the end user only needs to download a single file. Using CSS &lt;code&gt;background-image&lt;/code&gt; to display the proper graphic on your site.&lt;/p&gt;

&lt;p&gt;Ever have raw files from designers that are in PSD format and you want PNGs? You can export them with Cute Exporter. It caches all the individual PNG files to disk before it stitches them into a texture atlas. You can then easily grab them from the cache folder.&lt;/p&gt;

&lt;p&gt;But it's mostly cool for games. And making games in HTML5 is really accessible. &lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>art</category>
      <category>gui</category>
    </item>
    <item>
      <title>Asset pipeline updates</title>
      <dc:creator>Clay Murray</dc:creator>
      <pubDate>Wed, 08 Jul 2020 00:35:02 +0000</pubDate>
      <link>https://dev.to/powerc9000/asset-pipeline-updates-1cnb</link>
      <guid>https://dev.to/powerc9000/asset-pipeline-updates-1cnb</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.to/powerc9000/asset-pipeline-in-my-game-2ep6"&gt;In a previous post&lt;/a&gt; I talked about my asset pipeline.&lt;/p&gt;

&lt;p&gt;I have now made a few changes/improvements to the system.&lt;/p&gt;

&lt;h3&gt;
  
  
  SQLite
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.sqlite.org/index.html"&gt;SQLite&lt;/a&gt; is a pretty neat tool. It's a database system saved to a single file. I am using it to store information about my game assets.&lt;/p&gt;

&lt;p&gt;In my previous pipeline I was adding every PDF to a simple text line with a bit more info. It was easy and worked, but I started to want a little more data about assets individually.&lt;/p&gt;

&lt;p&gt;Even if certain assets are grouped together in a PDF I wanted to be able to add data to an asset on an individual level. Maybe different assets should have different scaling, or associated tags, or making animations easier.&lt;/p&gt;

&lt;p&gt;All this can be done in a simple text file, but it's fairly relational data so why not use a simple database to store the info.&lt;/p&gt;

&lt;p&gt;For my tables I made a few&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;raw_asset, the PDF file for an asset.&lt;/li&gt;
&lt;li&gt;game_asset, the individual assets as used in the game.&lt;/li&gt;
&lt;li&gt;asset_tag, tags for individual assets.&lt;/li&gt;
&lt;li&gt;animation, animation info.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's pretty simple. raw_assets speficy a name for the group of assets in it and the path to the PDF. &lt;br&gt;
Game assets have a raw_asset they belong to that specifies where in the PDF they are by path eg. &lt;code&gt;Group/layer name&lt;/code&gt;.&lt;br&gt;
Then game assets can have tags and what animation they belong to.&lt;br&gt;
Animations are just an id and a name.&lt;/p&gt;
&lt;h3&gt;
  
  
  Effects
&lt;/h3&gt;

&lt;p&gt;These changes have made the pipeline a bit simpler in some ways and added a bit more complexity to other things, but at the benefit of providing more robust information.&lt;/p&gt;

&lt;p&gt;We no longer need to do much inspection of the PDF to get groups and layers in ruby. We can just query the database about the raw assets and for each raw asset get its associated game assets.&lt;br&gt;
Since the game asset tells us in the raw asset where it is, we can just pass that info straight to gimp to export the PNG.&lt;/p&gt;

&lt;p&gt;Before the name and path of the asset in the sprites folder gave us metadata about it. &lt;code&gt;Harper/Front/walk_1.png&lt;/code&gt; said the group name and the facing direction and the animation and the frame.&lt;br&gt;
While giving some information it's hard to add more. &lt;br&gt;
With the database existing the script doesn't need to save to paths with names that encode data about our asset. The script just saves by the id from sqlite and the group name e.g &lt;code&gt;Harper/26.png&lt;/code&gt; (using the group name as a folder purely to make them easier to see in the finder). &lt;/p&gt;

&lt;p&gt;When it comes to packing the texture we have all we need to know: the asset id and where it is in the texture atlas. Outputting a much slimmer file that's is pure JSON from texturepacker. It also removes any weirdness with figuring out the frame number from the asset name in the custom formatter in texture packer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
{
    "frames": [

            {
                "filename":"battle/28",
                "frame":{"x":1,"y":3478,"w":500,"h":216},
                "trimmed":true
            }, 

            {
                "filename":"buildings/20",
                "frame":{"x":497,"y":1709,"w":242,"h":500},
                "trimmed":true
            }, 

            {
                "filename":"buildings/21",
                "frame":{"x":1,"y":564,"w":500,"h":412},
                "trimmed":true
            }
    ],
    "meta": {
        "image":"sprites.png"
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;I made a script with Node.js that takes the texture packer json file. Looks up the asset in the db and adds any other metadata we care about to it. Such as the frame number, the tags, the id and group.&lt;br&gt;
It still formats it as lua because I didn't want to change my engine to read json right now...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;return {
    frames = {
        {
            filename="battle/28",
            frame={
                x=1,
                y=3478,
                w=500,
                h=216
            },
            trimmed=true,
            tags={
                "battle","banner"
            },
            animationName="",
            id=28,
            name="battle_banner",
            group="battle",
            frameNumber=-1
        },{
            filename="buildings/20",
            frame={
                x=497,
                y=1709,
                w=242,
                h=500
            },
            trimmed=true,
            tags={
                "regular"
            },
            animationName="",
            id=20,
            name="some_building",
            group="buildings",
            frameNumber=-1
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Since SQLite can give us always unique ids that are consistent across runs for my assets, it makes it easy to build tables about animations in the engine. &lt;/p&gt;

&lt;p&gt;Previously when I loaded this data in my engine I was building up animation info and asset info in the same loops. It felt fragile and hacky. The engine now just loads all my assets info from the lua file then builds up animation info as a seperate step.&lt;/p&gt;

&lt;p&gt;After the engine loads all the asset info it has a consistent id for an asset that it can always reference in my game and ask the database questions about it if I need it to. (which I do!)&lt;/p&gt;

&lt;p&gt;With all the asset info being loaded the engine can build up the animation info. In my engine I just make a call with &lt;code&gt;popen&lt;/code&gt; and run &lt;code&gt;sqlite3 database.db "select * from animations"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This will produce a list containing an id and name for all animations. For each animation I can now query about which asset ids are assocated eg &lt;code&gt;sqlite3 database.db "select id from game_asset where animation_id=2"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now the engine will make up some animation info tables. Essentially a map of animation names and an array of assets ids that are part of the animation. &lt;/p&gt;

&lt;p&gt;Later in the gameplay code if the character is walking I can tell the engine just to use the animation named&lt;code&gt;"harper_front_walk"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Pretty fun!&lt;/p&gt;

&lt;p&gt;Anway here's a picture of a new things I drew Grambly&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--46iQKcIV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://claymurray.website/devlog/images/grambly.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--46iQKcIV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://claymurray.website/devlog/images/grambly.png" alt="Grambly is a weird looking red skeleton. He loves God and hates cops. Amen."&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>assets</category>
      <category>art</category>
    </item>
    <item>
      <title>Asset Pipeline in my Game</title>
      <dc:creator>Clay Murray</dc:creator>
      <pubDate>Tue, 30 Jun 2020 17:58:23 +0000</pubDate>
      <link>https://dev.to/powerc9000/asset-pipeline-in-my-game-2ep6</link>
      <guid>https://dev.to/powerc9000/asset-pipeline-in-my-game-2ep6</guid>
      <description>&lt;p&gt;Every game has assets, and somehow those assets have to get into the game. Here's what I do.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Creating the Assets
&lt;/h2&gt;

&lt;p&gt;So first step is obviously creating the assets. What I am doing is different from most people. I draw the assets in a sketchbook, then I scan those images into my computer.&lt;br&gt;
After that I take the scans and mask out all the parts I don't want, rotate stuff, and make everything feel real good.&lt;/p&gt;
&lt;h3&gt;
  
  
  Photoshop organization
&lt;/h3&gt;

&lt;p&gt;When making an asset for some entities you want animations to have animations on. And one of my goals is keeping the same character's asset data in the same PSD.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OLqxneaj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://claymurray.website/devlog/images/psd_layout.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OLqxneaj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://claymurray.website/devlog/images/psd_layout.png" alt="PSD Layout example"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So in my PSDs I just groups. With the name of the group being a direction (you can go 4 cardinal directions in my game) and a frame or still image in that direction. Animations cycles are specified via a name_framenumber syntax (eg walk_1)&lt;/p&gt;

&lt;p&gt;For other types of entities like buildings I don't really care to have animation frames. In those cases I don't use groups or anything.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Exporting the Assets
&lt;/h2&gt;

&lt;p&gt;Now that the assets have been created we want to export them. You could have a script that just looks for PSD files in a directory and exports them all. However, I want a bit more granularity in my process.&lt;br&gt;
I am using a really simple file format to specify the assets I want to use and some other information about them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;assets/raw/harper.psd harper group 75%
assets/raw/some_building.psd buildings single x500
assets/raw/the_store.psd buildings single 500x
assets/raw/robo_dude.psd robots single x400
assets/raw/pointy_boy.psd ui single x50
assets/raw/square_boy.psd robots single x400
assets/raw/roller_boy.psd robots single x400
assets/raw/skelly_boy_blue.psd skeletons single x400
assets/raw/dewey_cheetum_howe.psd buildings single 500x
assets/raw/battle_banner.psd battle single 500x
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The format is just space seperated items in a simple text file. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Path to PSD&lt;/li&gt;
&lt;li&gt;Grouping to save to&lt;/li&gt;
&lt;li&gt;If this PSD uses groups or if it just has a single layer.&lt;/li&gt;
&lt;li&gt;imagemagick scale factor.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wrote a ruby script that takes this info and reads the lines.&lt;/p&gt;

&lt;p&gt;Over each line it loads the resulting PSD, finds all the layers we want to export and prepares the directories.&lt;/p&gt;

&lt;p&gt;I do a check to see if the PSD is newer than the result PNG or the result PNG doesnt exist. That way we only have to run the export on assets that have changed.&lt;/p&gt;

&lt;p&gt;If it's a grouped PSD each group is a sub folder inside the group name and the layer name is the exported png name (eg harper/Back/walk_2.png).&lt;/p&gt;

&lt;p&gt;If it's a single layer we save it to the group folder with the PSD file name as the resulting png name (eg buildings/the_store.png).&lt;/p&gt;

&lt;h3&gt;
  
  
  Saving to PNG
&lt;/h3&gt;

&lt;p&gt;The ruby PSD library I am using is too slow on its own to save PSDs at a reasonable rate. At first I tried using imagemagick but it doesnt accept layer names, only the scene index. Like: &lt;code&gt;convert file.psd[1] out.png&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;I could never find a way to map between a imagemagick scene and a layer in my ruby PSD stuff but Imagemagick is fine for single layer PSDs and I use it to export those.&lt;/p&gt;

&lt;h4&gt;
  
  
  GIMP
&lt;/h4&gt;

&lt;p&gt;Well as it turns out gimp has a scripting interface that doesn't even have to show a GUI. I figured out how to use it to export my grouped PSD files. &lt;br&gt;
Here's the process&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Ruby passes the group and layer name to gimp along with the path to the PSD and a path to save the final image to.&lt;/li&gt;
&lt;li&gt;Gimp finds that layer, makes sure it is visible and saves it to the path we gave.

&lt;ul&gt;
&lt;li&gt;this process is somewhat weird. If you just try to save the layer and only the layer it won't include the clipping mask. So you need to make the layer you want visible and hide all the other layers. Then you can merge visible layers and save the result of that. &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's what my resulting GIMP script looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash
set -e

# Start gimp with python-fu batch-interpreter
/Applications/GIMP-2.10.app/Contents/MacOS/gimp -i --batch-interpreter=python-fu-eval -b - &amp;lt;&amp;lt; EOF
import gimpfu
import traceback

def convert(filename, groupName, layerName, out):
    try:
            print(filename, groupName, layerName, out)
            img = pdb.gimp_file_load(filename, filename)
            if len(groupName) == 0:
                layer = pdb.get_layer_by_name(layerName)
                pdb.gimp_file_save(img, layer, out, out)
            else:
                for layer in img.layers:
                    if pdb.gimp_item_get_name(layer) == groupName:
                        children = pdb.gimp_item_get_children(layer)[1]
                        for child in children:
                            item = gimp.Item.from_id(child)
                            if pdb.gimp_item_get_name(item).split(" ")[0] == layerName:
                                pdb.gimp_item_set_visible(item, True)
                            else:
                                pdb.gimp_item_set_visible(item, False)
                    else:
                        pdb.gimp_item_set_visible(layer, False)

                nLayer = pdb.gimp_image_merge_visible_layers(img, CLIP_TO_IMAGE)
                pdb.gimp_file_save(img, nLayer, out, out)
            pdb.gimp_image_delete(img)
            pdb.gimp_quit(0)
    except RuntimeError as err:
                print(traceback.print_exc())
                pdb.gimp_quit(1)

convert('$1', '$2', '$3', '$4')
EOF
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;(at one point I was going to use gimp for the group and single layer path so the logic is still there)&lt;/p&gt;

&lt;h4&gt;
  
  
  Back to ruby
&lt;/h4&gt;

&lt;p&gt;After gimp has our file saved or image magick exported our layer. We write an entry to another txt file this is a list of files that we want imagemagick to resize and the size to make it.&lt;/p&gt;

&lt;p&gt;It ends up something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;assets/sprites/harper/back/walk_2.png 75%
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  Imagemagick
&lt;/h4&gt;

&lt;p&gt;With a simple bash script we take each line of the file and resize it according to the sizing info in that line. &lt;/p&gt;

&lt;p&gt;It can be passed whatever sizing info imagemagick can accept. Percentages, WIDTHxHEIGHT, xHEIGHT, WIDTHx etc.&lt;/p&gt;

&lt;p&gt;Now we have all our images exported to folders.&lt;/p&gt;

&lt;h3&gt;
  
  
  Texture atlas
&lt;/h3&gt;

&lt;p&gt;Now I want the images packed into one texture atlas PNG. I use TexturePacker Pro. &lt;small&gt;I paid for pro so I could make a custom exporter.&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;TexturePacker can export from the command line making it easy to include in the automated pipeline.&lt;/p&gt;

&lt;p&gt;All texturepacker is doing is looking for files in the sprites directory and adding them to the texture atlas.&lt;/p&gt;

&lt;p&gt;TexturePacker exports to a lua file that the engine will read.&lt;/p&gt;

&lt;h2&gt;
  
  
  Into the Game
&lt;/h2&gt;

&lt;p&gt;Now we have all our stuff exported into a texture atlas with a lua file describing the bounds and names of all the files. I embedded lua in the engine and we read the file and get the resulting data from it into odin. At some point I could reload assets on the fly. I just don't do that yet.&lt;/p&gt;

&lt;p&gt;The cool part of all this for me is having a fully automated pipeline. No exporting from photoshop or opening other tools. Just works from one command.&lt;/p&gt;

&lt;p&gt;Video of it running.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/ObecwEU0QGc"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>assets</category>
      <category>art</category>
    </item>
    <item>
      <title>What do you use for server logging?</title>
      <dc:creator>Clay Murray</dc:creator>
      <pubDate>Tue, 03 Dec 2019 20:32:13 +0000</pubDate>
      <link>https://dev.to/powerc9000/what-do-you-use-for-server-logging-gaj</link>
      <guid>https://dev.to/powerc9000/what-do-you-use-for-server-logging-gaj</guid>
      <description>&lt;p&gt;I am looking for a good logging solution for my newest project. For reference I just nodejs and docker. Scanning docker logs isn't the best solution. On previous projects I used cloudwatch from AWS (which is great) but I want to avoid using any AWS services on this project. Some criteria I'd enjoy.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Picks up directly from docker logs no need to change my application code.&lt;/li&gt;
&lt;li&gt;Well priced. I won't be generating that many logs.&lt;/li&gt;
&lt;li&gt;Mildly searchable. I don't need to look back on logs that often.&lt;/li&gt;
&lt;li&gt;30 day retention. Don't need much more than that.&lt;/li&gt;
&lt;li&gt;Cloud hosted. I don't want to setup more infrastructure for logging.&lt;/li&gt;
&lt;li&gt;Preferably run by a smaller company. I like using services away from huge tech companies. For instance I use postmark for emailing. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What do any of you use for your logging services. What do you like about them? &lt;/p&gt;

</description>
      <category>discuss</category>
      <category>node</category>
      <category>docker</category>
    </item>
    <item>
      <title>Incoming email hooks and you!</title>
      <dc:creator>Clay Murray</dc:creator>
      <pubDate>Mon, 11 Nov 2019 16:10:31 +0000</pubDate>
      <link>https://dev.to/powerc9000/incoming-email-hooks-and-you-4fpg</link>
      <guid>https://dev.to/powerc9000/incoming-email-hooks-and-you-4fpg</guid>
      <description>&lt;p&gt;Sometimes when you are making your cool app thing you want to accept email. This could be for a myriad of reasons. &lt;/p&gt;

&lt;p&gt;Consider the Github use case. When someone comments on an issue you are watching you will receive an email. If you reply to the email it adds a comment to the issue!&lt;/p&gt;

&lt;p&gt;Let's learn about some cool email headers and how we might go about building that ourselves.&lt;/p&gt;

&lt;p&gt;I'll be using &lt;a href="https://postmarkapp.com/"&gt;Postmark&lt;/a&gt; as the email sender/receiver. Most other email services like them also have this functionality. I just think they are cool and use them for my own projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;I'm going to skip a bit past the initial steps of creating an account and a server. Postmark has that well documented. &lt;/p&gt;

&lt;p&gt;What we care about is the incoming email hook.&lt;/p&gt;

&lt;p&gt;First we go to default inbound stream.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--X87jAcOA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/gpved8da733xb5mbma67.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--X87jAcOA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/gpved8da733xb5mbma67.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then in the settings tab the inbound email hook&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Hc1-F31Y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/0tg8svqc9rea4bepxuyz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Hc1-F31Y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/0tg8svqc9rea4bepxuyz.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We'll also want to set an inbound email domain. You'll have to look up &lt;a href="https://postmarkapp.com/developer/user-guide/inbound/inbound-domain-forwarding"&gt;How to set that up in your DNS&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jChPsHzn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/ujd50h9q1bntzhtoh1lx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jChPsHzn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/ujd50h9q1bntzhtoh1lx.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Okay so we have our quick setup. Postmark has great docs so check those out for more setup info. Though to note it's very important to have a custom inbound domain to accomplish what we want to do!&lt;/p&gt;

&lt;h2&gt;
  
  
  Sending our email
&lt;/h2&gt;

&lt;p&gt;Let's send our email to the user. What do we want to track how we will know it is them?&lt;/p&gt;

&lt;h3&gt;
  
  
  Message-ID
&lt;/h3&gt;

&lt;p&gt;The message id is a unique id for every email sent. It's a header that will be sent with any email to or from you. You can see the message id in Gmail by showing the original email. They can be any value but are typically formated as &lt;code&gt;&amp;lt;some-random-id@sender.com&amp;gt;&lt;/code&gt; and email I got from twitter had the &lt;code&gt;Message-ID&lt;/code&gt;: &lt;code&gt;&amp;lt;42.0B.41170.86A79BB5@twitter.com&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  In-Reply-To
&lt;/h3&gt;

&lt;p&gt;This email header is the starting point for how email clients thread emails. When you reply to an email your client puts the &lt;code&gt;Message-ID&lt;/code&gt; of the email sent to you in the &lt;code&gt;In-Reply-To&lt;/code&gt; header&lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;p&gt;This header contains the &lt;code&gt;Message-ID&lt;/code&gt;s of all the emails in the email thread. Every time a new email is send in a thread the &lt;code&gt;Message-ID&lt;/code&gt; of the email being replied to is also appened to the &lt;code&gt;References&lt;/code&gt; field&lt;/p&gt;

&lt;h3&gt;
  
  
  Reply-To
&lt;/h3&gt;

&lt;p&gt;When you send an email you can set a reply to address that differs from the to address you sent the email from. These headers are going to enable our workflow.&lt;/p&gt;

&lt;p&gt;So let's send our email.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Require:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;postmark&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;postmark&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Send an email:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;postmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POSTMARK-SERVER-API-TOKEN-HERE&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;emailId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;generateAndStoreNewEmailId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;commentThread&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendEmail&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;From&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;sender@example.com&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;To&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;recipient@example.com&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;Reply-To&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`inbound+&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;emailId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;@inbound.ourdomain.com`&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Subject&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;Test&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;TextBody&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;Hello from Postmark!&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;Headers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Message-ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;emailId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;@inbound.ourdomain.com&amp;gt;`&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;Okay so notice we set our Reply-To to an inbound email with an id we generated. We also want to store the user we are sending this email to and what comment thread.&lt;/p&gt;

&lt;p&gt;Now we wait for a reply&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling the inbound email
&lt;/h2&gt;

&lt;p&gt;When any email is sent to our inbound email domain we will get a post request to our webhook. The entire message is quite big &lt;a href="https://postmarkapp.com/developer/webhooks/inbound-webhook#inbound-webhook-data"&gt;you can see it here&lt;/a&gt;. We just care about a few fields. We will use them to lookup the context for this email and add the comment.&lt;/p&gt;

&lt;p&gt;The reason we used the &lt;code&gt;inbound+${emailId}&lt;/code&gt; in our &lt;code&gt;Reply-To&lt;/code&gt; was because postmark is nice enough to parse that id off for us and add it to a field called &lt;code&gt;MailboxHash&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now we can lookup the thread this email is talking about from the emailId.&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;emailInfo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;getEmailInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inboundEmail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MailboxHash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Postmark is also nice enough to parse the email body and give us just the reply text. We will use that for the commend body!&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inboundEmail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TextBody&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Then we can do something.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;createCommentInThread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;emailInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commentThreadId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;emailInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;body&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;We also stored the email id in the &lt;code&gt;Message-ID&lt;/code&gt; so if for some reason we can't find our comment thread via &lt;code&gt;Reply-To&lt;/code&gt; we can search the &lt;code&gt;References&lt;/code&gt; or &lt;code&gt;In-Reply-To&lt;/code&gt; fields to check as well.&lt;/p&gt;

&lt;p&gt;This is the general strategy that &lt;a href="https://docs.gitlab.com/ee/administration/reply_by_email.html"&gt;GitLab uses&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This was a pretty high level overview but hopefully it helps you understand how easy it can be to make your app accept incoming emails!&lt;/p&gt;

</description>
      <category>email</category>
      <category>webdev</category>
      <category>node</category>
      <category>postmarkapp</category>
    </item>
    <item>
      <title>Building JuniperCity.com</title>
      <dc:creator>Clay Murray</dc:creator>
      <pubDate>Mon, 04 Nov 2019 18:41:38 +0000</pubDate>
      <link>https://dev.to/dropconfig/building-junipercity-com-1faf</link>
      <guid>https://dev.to/dropconfig/building-junipercity-com-1faf</guid>
      <description>&lt;p&gt;So I started a new thing: &lt;a href="https://junipercity.com"&gt;Juniper City&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;While I also want to shamelessly plug it. I also want to talk about the tech in it and decisions that were made.&lt;/p&gt;

&lt;h2&gt;
  
  
  The What
&lt;/h2&gt;

&lt;p&gt;Juniper City is a place to post and manage your events. The idea is to get away from having to use Facebook and making it really easy for people not on the platform to use. &lt;/p&gt;

&lt;p&gt;There's some core concepts I wanted to keep with.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No account creation.&lt;/li&gt;
&lt;li&gt;Invite people where they are.&lt;/li&gt;
&lt;li&gt;Usable over email.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  No account creation
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Technically&lt;/em&gt; you do create an account but I wanted it to be easy. We don't have passwords. You put in your email and we send you a one time password to login. &lt;/p&gt;

&lt;p&gt;The goal is very little friction for people you invite to use the site. So when you do invite people they get a link with a key in the query params. That key logs them in. They didn't have to think about anything, and no redirect to do what they wanted to do.&lt;/p&gt;

&lt;h3&gt;
  
  
  Invite people where they are.
&lt;/h3&gt;

&lt;p&gt;I wanted this to be something you could use for your entire family or group. Not everyone has Facebook or any number of social accounts. But almost everyone has an email or can get texts.&lt;/p&gt;

&lt;p&gt;You invite people not by a username we have, but their email or phone. In the background if we don't have them on file we will make a user for them. Like mentioned before we have links that auto log them in, but since we made a user and have no passwords, they can log in at anytime without it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Usable over email
&lt;/h3&gt;

&lt;p&gt;I didn't quite launch with everything I wanted for this part. We currently send out invites over email but I wanted to do more. &lt;/p&gt;

&lt;p&gt;So coming soon the future, you will be able to email Juniper City to create your event without ever visiting our site. &lt;/p&gt;

&lt;p&gt;The people in the to field of the email get invited. You never have to even copy and paste over emails this way. Your email client is now our fronted client.&lt;/p&gt;

&lt;p&gt;The big goal is trying to make useful open tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  The How
&lt;/h2&gt;

&lt;p&gt;So how was it built?&lt;/p&gt;

&lt;p&gt;So the stack for the site is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Docker&lt;/li&gt;
&lt;li&gt;Nodejs&lt;/li&gt;
&lt;li&gt;Hapi&lt;/li&gt;
&lt;li&gt;Postgres&lt;/li&gt;
&lt;li&gt;Redis&lt;/li&gt;
&lt;li&gt;Stimulusjs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The entire stack is one monolith. No micro-services and we use server side rendering. It makes it simple to program for and deploy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Frontend
&lt;/h3&gt;

&lt;p&gt;This is a really simple frontend and we use a really simple framework. I've talked about &lt;a href="https://stimulusjs.org/"&gt;Stimulusjs&lt;/a&gt; in a lot in my other posts so I won't belabor the point. &lt;/p&gt;

&lt;p&gt;We use &lt;a href="https://ejs.co/"&gt;ejs&lt;/a&gt; for server side templates. (Though I am thinking of switching to &lt;a href="https://mozilla.github.io/nunjucks/"&gt;Nunjucks&lt;/a&gt; it just seems nicer).&lt;/p&gt;

&lt;p&gt;The total JavaScript bundle in production is around 350 kb and 65 kb minified. That's tiny. We don't do a lot in the frontend so why bloat it?&lt;/p&gt;

&lt;p&gt;All our frontend is doing is just a few AJAX requests as well as hiding and showing things. Stimulus really does help keep this organized. &lt;/p&gt;

&lt;p&gt;Keeping the site server side rendered with minimal JavaScript give a big advantage. Once we respond from the server, we're serving up blazing fast pages. Taking advantage of &lt;a href="https://github.com/turbolinks/turbolinks"&gt;Turbolinks&lt;/a&gt; as well to make it feel single page. Can you get similar results with React or Vue? Yes, but it's not as EASY.&lt;/p&gt;

&lt;p&gt;From the styling end we use &lt;a href="https://tailwindcss.com/"&gt;Tailwindcss&lt;/a&gt; it's just awesome. Worth checking out. Makes putting together designs an absolute breeze.&lt;/p&gt;

&lt;h4&gt;
  
  
  Tools
&lt;/h4&gt;

&lt;p&gt;For bundling we use &lt;a href="https://parceljs.org/"&gt;Parcel&lt;/a&gt;. I don't have a very strong opinion on this but parcel is very near it's promise of zero config. &lt;/p&gt;

&lt;p&gt;In production to keep the CSS small (tailwind has a lot of unused css) we use &lt;a href="https://github.com/FullHuman/purgecss"&gt;PurgeCSS&lt;/a&gt;. This pushes our css to 7.5 kb and 2 kb gziped.&lt;/p&gt;

&lt;h3&gt;
  
  
  Server side.
&lt;/h3&gt;

&lt;p&gt;The stack here is pretty standard and easy. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nodejs as the application logic. &lt;/li&gt;
&lt;li&gt;Postgres as our database. &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/dropconfig/simplifying-my-old-event-bus-architecture-with-bull-4mf4"&gt;Redis for job queuing&lt;/a&gt;. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We integrate with &lt;a href="https://postmarkapp.com/"&gt;Postmark&lt;/a&gt; for email delivery. For SMS delivery we use &lt;a href="https://textbelt.com/"&gt;Textbelt&lt;/a&gt;. Also note that it will only send texts in the US.&lt;/p&gt;

&lt;p&gt;I found Textbelt after a large amount of searching. &lt;/p&gt;

&lt;p&gt;Some other options I didn't like.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Twilio

&lt;ul&gt;
&lt;li&gt;Wow is this complex. Do I have to buy a number? How can I JUST send a text&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;AWS SNS

&lt;ul&gt;
&lt;li&gt;Simple and cheap but I was searching to get away from using AWS.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Queues
&lt;/h4&gt;

&lt;p&gt;We make use of queues and task workers. &lt;a href="https://dev.to/dropconfig/simplifying-my-old-event-bus-architecture-with-bull-4mf4"&gt;Take a look at this thing I wrote&lt;/a&gt; for an in depth about it.&lt;/p&gt;

&lt;p&gt;Anytime we want to send an email or a text, we put it into a queue. Incoming web hooks? Queues. In essence anything you don't want to do right now. Queue it baby! The great thing is our current system has great support for running things at a later date as well. Things like cron jobs fit this use case.&lt;/p&gt;

&lt;h4&gt;
  
  
  Deploying
&lt;/h4&gt;

&lt;p&gt;We package the server and frontend code in a docker container (Docker is so great).&lt;/p&gt;

&lt;p&gt;Right now we server all our assets from the server. For the future we'll likely move static assets (CSS, JS, images) to a CDN. It's good enough right now.&lt;/p&gt;

&lt;p&gt;Hosting is on Digital Ocean. Amazon is kinda evil so I wanted to try something else. What Digital Ocean lacks in configuration and power it makes up for in simplicity. &lt;/p&gt;

&lt;p&gt;If you couldn't tell simplicity is a core tenant of this entire project.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We Build the project using docker. &lt;/li&gt;
&lt;li&gt;Upload the image to a registry.&lt;/li&gt;
&lt;li&gt;Deploy with docker compose. 

&lt;ul&gt;
&lt;li&gt;I learned about this option in compose &lt;code&gt;-H "ssh://user@server"&lt;/code&gt;. As long as you have SSH access to your remote machine it's the same as running the commands on your own machine. In the past I would copy the env files and compose files over to the remote machine as part of the build step. Using this new method you don't have to do ANY of that. &lt;/li&gt;
&lt;li&gt;&lt;code&gt;docker-compose -H "ssh://user@server" pull image&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docker-compose -H "ssh://user@server" up -d&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Again really simple.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I hope you liked that overview of what &lt;a href="https://junipercity.com"&gt;Juniper City&lt;/a&gt; is. Maybe you can find a use for it!&lt;/p&gt;

</description>
      <category>web</category>
      <category>node</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Sending emails with templates using MJML</title>
      <dc:creator>Clay Murray</dc:creator>
      <pubDate>Wed, 23 Oct 2019 18:11:33 +0000</pubDate>
      <link>https://dev.to/powerc9000/sending-emails-with-templates-using-mjml-318e</link>
      <guid>https://dev.to/powerc9000/sending-emails-with-templates-using-mjml-318e</guid>
      <description>&lt;p&gt;Sending emails is something a lot of web apps like to do. Password resets, notifications, promotions etc. &lt;/p&gt;

&lt;p&gt;One of the biggest annoyances in sending emails is HTML EMAILS! They are very messy, ugly, and impossible to figure out. &lt;/p&gt;

&lt;p&gt;In the past we would design our emails using Mailchimp then export them as an HTML email. This results in a convoluted mess of HTML that no one wants to make minor edits to. &lt;/p&gt;

&lt;p&gt;Can we do better? Of course we can that's why I wrote this article! &lt;/p&gt;

&lt;h2&gt;
  
  
  MJML
&lt;/h2&gt;

&lt;p&gt;Enter &lt;a href="https://mjml.io/"&gt;MJML&lt;/a&gt;. It's a neat little library to make it easier to keep your HTML emails as code without going crazy!&lt;/p&gt;

&lt;p&gt;This is just a quick example from their site:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;mjml&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;mj-body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;mj-section&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;mj-column&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;mj-image&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"100px"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://mjml.io/assets/img/logo-small.png"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/mj-image&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;mj-divider&lt;/span&gt; &lt;span class="na"&gt;border-color=&lt;/span&gt;&lt;span class="s"&gt;"#F45E43"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/mj-divider&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;mj-text&lt;/span&gt; &lt;span class="na"&gt;font-size=&lt;/span&gt;&lt;span class="s"&gt;"20px"&lt;/span&gt; &lt;span class="na"&gt;color=&lt;/span&gt;&lt;span class="s"&gt;"#F45E43"&lt;/span&gt; &lt;span class="na"&gt;font-family=&lt;/span&gt;&lt;span class="s"&gt;"helvetica"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Hello World&lt;span class="nt"&gt;&amp;lt;/mj-text&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/mj-column&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/mj-section&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/mj-body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/mjml&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;As you can see, it is very readable and HTML like. Much easier to edit and maintain!&lt;/p&gt;

&lt;p&gt;They even have a &lt;a href="https://mjml.io/try-it-live"&gt;free online editor&lt;/a&gt; to see what your email will look like!&lt;/p&gt;

&lt;p&gt;MJML will take the code that you write and transform it into something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!doctype html&amp;gt;
&amp;lt;html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office"&amp;gt;

&amp;lt;head&amp;gt;
  &amp;lt;title&amp;gt; &amp;lt;/title&amp;gt;
  &amp;lt;!--[if !mso]&amp;gt;&amp;lt;!-- --&amp;gt;
  &amp;lt;meta http-equiv="X-UA-Compatible" content="IE=edge"&amp;gt;
  &amp;lt;!--&amp;lt;![endif]--&amp;gt;
  &amp;lt;meta http-equiv="Content-Type" content="text/html; charset=UTF-8"&amp;gt;
  &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1"&amp;gt;
  &amp;lt;style type="text/css"&amp;gt;
    #outlook a {
      padding: 0;
    }

    .ReadMsgBody {
      width: 100%;
    }

    .ExternalClass {
      width: 100%;
    }

    .ExternalClass * {
      line-height: 100%;
    }

    body {
      margin: 0;
      padding: 0;
      -webkit-text-size-adjust: 100%;
      -ms-text-size-adjust: 100%;
    }

    table,
    td {
      border-collapse: collapse;
      mso-table-lspace: 0pt;
      mso-table-rspace: 0pt;
    }

    img {
      border: 0;
      height: auto;
      line-height: 100%;
      outline: none;
      text-decoration: none;
      -ms-interpolation-mode: bicubic;
    }

    p {
      display: block;
      margin: 13px 0;
    }
  &amp;lt;/style&amp;gt;
  &amp;lt;!--[if !mso]&amp;gt;&amp;lt;!--&amp;gt;
  &amp;lt;style type="text/css"&amp;gt;
    @media only screen and (max-width:480px) {
      @-ms-viewport {
        width: 320px;
      }
      @viewport {
        width: 320px;
      }
    }
  &amp;lt;/style&amp;gt;
  &amp;lt;!--&amp;lt;![endif]--&amp;gt;
  &amp;lt;!--[if mso]&amp;gt;
        &amp;lt;xml&amp;gt;
        &amp;lt;o:OfficeDocumentSettings&amp;gt;
          &amp;lt;o:AllowPNG/&amp;gt;
          &amp;lt;o:PixelsPerInch&amp;gt;96&amp;lt;/o:PixelsPerInch&amp;gt;
        &amp;lt;/o:OfficeDocumentSettings&amp;gt;
        &amp;lt;/xml&amp;gt;
        &amp;lt;![endif]--&amp;gt;
  &amp;lt;!--[if lte mso 11]&amp;gt;
        &amp;lt;style type="text/css"&amp;gt;
          .outlook-group-fix { width:100% !important; }
        &amp;lt;/style&amp;gt;
        &amp;lt;![endif]--&amp;gt;
  &amp;lt;style type="text/css"&amp;gt;
    @media only screen and (min-width:480px) {
      .mj-column-per-100 {
        width: 100% !important;
        max-width: 100%;
      }
    }
  &amp;lt;/style&amp;gt;
  &amp;lt;style type="text/css"&amp;gt;
    @media only screen and (max-width:480px) {
      table.full-width-mobile {
        width: 100% !important;
      }
      td.full-width-mobile {
        width: auto !important;
      }
    }
  &amp;lt;/style&amp;gt;
&amp;lt;/head&amp;gt;

&amp;lt;body&amp;gt;
  &amp;lt;div style=""&amp;gt;
    &amp;lt;!--[if mso | IE]&amp;gt;
      &amp;lt;table
         align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
      &amp;gt;
        &amp;lt;tr&amp;gt;
          &amp;lt;td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"&amp;gt;
      &amp;lt;![endif]--&amp;gt;
    &amp;lt;div style="Margin:0px auto;max-width:600px;"&amp;gt;
      &amp;lt;table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;"&amp;gt;
        &amp;lt;tbody&amp;gt;
          &amp;lt;tr&amp;gt;
            &amp;lt;td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;vertical-align:top;"&amp;gt;
              &amp;lt;!--[if mso | IE]&amp;gt;
                  &amp;lt;table role="presentation" border="0" cellpadding="0" cellspacing="0"&amp;gt;

        &amp;lt;tr&amp;gt;

            &amp;lt;td
               class="" style="vertical-align:top;width:600px;"
            &amp;gt;
          &amp;lt;![endif]--&amp;gt;
              &amp;lt;div class="mj-column-per-100 outlook-group-fix" style="font-size:13px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;"&amp;gt;
                &amp;lt;table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%"&amp;gt;
                  &amp;lt;tr&amp;gt;
                    &amp;lt;td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;"&amp;gt;
                      &amp;lt;table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;"&amp;gt;
                        &amp;lt;tbody&amp;gt;
                          &amp;lt;tr&amp;gt;
                            &amp;lt;td style="width:100px;"&amp;gt; &amp;lt;img height="auto" src="https://mjml.io/assets/img/logo-small.png" style="border:0;display:block;outline:none;text-decoration:none;height:auto;width:100%;" width="100" /&amp;gt; &amp;lt;/td&amp;gt;
                          &amp;lt;/tr&amp;gt;
                        &amp;lt;/tbody&amp;gt;
                      &amp;lt;/table&amp;gt;
                    &amp;lt;/td&amp;gt;
                  &amp;lt;/tr&amp;gt;
                  &amp;lt;tr&amp;gt;
                    &amp;lt;td style="font-size:0px;padding:10px 25px;word-break:break-word;"&amp;gt;
                      &amp;lt;p style="border-top:solid 4px #F45E43;font-size:1;margin:0px auto;width:100%;"&amp;gt; &amp;lt;/p&amp;gt;
                      &amp;lt;!--[if mso | IE]&amp;gt;
        &amp;lt;table
           align="center" border="0" cellpadding="0" cellspacing="0" style="border-top:solid 4px #F45E43;font-size:1;margin:0px auto;width:550px;" role="presentation" width="550px"
        &amp;gt;
          &amp;lt;tr&amp;gt;
            &amp;lt;td style="height:0;line-height:0;"&amp;gt;
              &amp;amp;nbsp;
            &amp;lt;/td&amp;gt;
          &amp;lt;/tr&amp;gt;
        &amp;lt;/table&amp;gt;
      &amp;lt;![endif]--&amp;gt;
                    &amp;lt;/td&amp;gt;
                  &amp;lt;/tr&amp;gt;
                  &amp;lt;tr&amp;gt;
                    &amp;lt;td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;"&amp;gt;
                      &amp;lt;div style="font-family:helvetica;font-size:20px;line-height:1;text-align:left;color:#F45E43;"&amp;gt; Hello World &amp;lt;/div&amp;gt;
                    &amp;lt;/td&amp;gt;
                  &amp;lt;/tr&amp;gt;
                &amp;lt;/table&amp;gt;
              &amp;lt;/div&amp;gt;
              &amp;lt;!--[if mso | IE]&amp;gt;
            &amp;lt;/td&amp;gt;

        &amp;lt;/tr&amp;gt;

                  &amp;lt;/table&amp;gt;
                &amp;lt;![endif]--&amp;gt;
            &amp;lt;/td&amp;gt;
          &amp;lt;/tr&amp;gt;
        &amp;lt;/tbody&amp;gt;
      &amp;lt;/table&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;!--[if mso | IE]&amp;gt;
          &amp;lt;/td&amp;gt;
        &amp;lt;/tr&amp;gt;
      &amp;lt;/table&amp;gt;
      &amp;lt;![endif]--&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;

&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;AHHHHH!!!! That's just terrifying. We thank MJML for fighting the demons for us! &lt;/p&gt;

&lt;p&gt;Now we can transform our MJML to HTML but it's currently static.&lt;/p&gt;

&lt;p&gt;So what about templating you may ask?&lt;/p&gt;

&lt;p&gt;Well...&lt;/p&gt;

&lt;h2&gt;
  
  
  Templating
&lt;/h2&gt;

&lt;p&gt;We still probably want to be able to use our MJML to make email templates. We want nice things like our user's name and custom links. Good web stuff.&lt;/p&gt;

&lt;p&gt;For that I use &lt;a href="https://github.com/janl/mustache.js/"&gt;mustache&lt;/a&gt;. It's fairly simple to use:&lt;/p&gt;

&lt;p&gt;Our template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;mjml&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;mj-body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;mj-section&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;mj-column&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;mj-image&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"100px"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://mjml.io/assets/img/logo-small.png"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/mj-image&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;mj-divider&lt;/span&gt; &lt;span class="na"&gt;border-color=&lt;/span&gt;&lt;span class="s"&gt;"#F45E43"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/mj-divider&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;mj-text&lt;/span&gt; &lt;span class="na"&gt;font-size=&lt;/span&gt;&lt;span class="s"&gt;"20px"&lt;/span&gt; &lt;span class="na"&gt;color=&lt;/span&gt;&lt;span class="s"&gt;"#F45E43"&lt;/span&gt; &lt;span class="na"&gt;font-family=&lt;/span&gt;&lt;span class="s"&gt;"helvetica"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Hello {{user}}&lt;span class="nt"&gt;&amp;lt;/mj-text&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/mj-column&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/mj-section&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/mj-body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/mjml&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Our code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;mustache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mustache&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;templateData&lt;/span&gt; &lt;span class="o"&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;user&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;John&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;renderedMJML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mustache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mjmlTemplate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;templateData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now we have filled in our template with mustache. But we are still in the MJML format. &lt;/p&gt;

&lt;p&gt;Why did we do that? Well MJML makes huge transformations to the code we hand to it. If we try to run mustache &lt;em&gt;after&lt;/em&gt; we convert to HTML we will lose our ability to use mustache.&lt;/p&gt;

&lt;p&gt;Luckily mustache doesn't much care what type of document we throw at it. It only cares about &lt;code&gt;{{}}&lt;/code&gt;. (Incidentally this allows you to use mustache in many other applications including JSON)&lt;/p&gt;

&lt;p&gt;Let's now convert from MJML to HTML.&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;mjml&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mjml&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;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;  &lt;span class="nx"&gt;mjml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;renderedMJML&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// don't forget the `.html`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now we have some HTML but we still need to...&lt;/p&gt;

&lt;h2&gt;
  
  
  Sending email
&lt;/h2&gt;

&lt;p&gt;Okay we now have an HTML template. We want to send it. &lt;br&gt;
I am going to use the &lt;a href="https://postmarkapp.com/developer/user-guide/sending-email/sending-with-api"&gt;Postmark Api&lt;/a&gt; because it's really easy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;fetch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node-fetch&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.postmarkapp.com/email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&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="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;Accept&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="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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;X-Postmark-Server-Token&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;server token&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;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;To&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;example@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;From&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;example2@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;Subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;This is a test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;HtmlBody&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;html&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;There you go. &lt;/p&gt;

&lt;p&gt;Full Javascript&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;fetch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node-fetch&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;mustache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mustache&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;mjml&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mjml&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;templateData&lt;/span&gt; &lt;span class="o"&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;user&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;John&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;mjmlTemplate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
&amp;lt;mjml&amp;gt;
  &amp;lt;mj-body&amp;gt;
    &amp;lt;mj-section&amp;gt;
      &amp;lt;mj-column&amp;gt;
        &amp;lt;mj-image width="100px" src="https://mjml.io/assets/img/logo-small.png"&amp;gt;&amp;lt;/mj-image&amp;gt;
        &amp;lt;mj-divider border-color="#F45E43"&amp;gt;&amp;lt;/mj-divider&amp;gt;
        &amp;lt;mj-text font-size="20px" color="#F45E43" font-family="helvetica"&amp;gt;Hello {{user}}&amp;lt;/mj-text&amp;gt;
      &amp;lt;/mj-column&amp;gt;
    &amp;lt;/mj-section&amp;gt;
  &amp;lt;/mj-body&amp;gt;
&amp;lt;/mjml&amp;gt;
`&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;renderedMJML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mustache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mjmlTemplate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;templateData&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;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;  &lt;span class="nx"&gt;mjml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;renderedMJML&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// don't forget the `.html`&lt;/span&gt;


&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.postmarkapp.com/email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&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="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;Accept&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="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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;X-Postmark-Server-Token&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;server token&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;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;To&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;example@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;From&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;example2@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;Subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;This is a test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;HtmlBody&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;html&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;



</description>
      <category>webdev</category>
      <category>node</category>
    </item>
    <item>
      <title>Simplifying my old event bus architecture with Bull</title>
      <dc:creator>Clay Murray</dc:creator>
      <pubDate>Wed, 09 Oct 2019 20:12:18 +0000</pubDate>
      <link>https://dev.to/dropconfig/simplifying-my-old-event-bus-architecture-with-bull-4mf4</link>
      <guid>https://dev.to/dropconfig/simplifying-my-old-event-bus-architecture-with-bull-4mf4</guid>
      <description>&lt;h2&gt;
  
  
  The why
&lt;/h2&gt;

&lt;p&gt;So before &lt;a href="https://dev.to/dropconfig/aws-sns-sqs-event-bus-8mf"&gt;I posted&lt;/a&gt; about an event bus with AWS SNS and SQS. I think it works really well and it's pretty cool. It did have some rough edges that bothered me though. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There was a lot of manual setup in production.

&lt;ul&gt;
&lt;li&gt;There are scripts etc that can do this but I was having to make the queue and SNS topic and link them for every new queue I wanted to make&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Local dev was messy

&lt;ul&gt;
&lt;li&gt;There are fairly solid Docker images that imitate SNS and SQS but the setup in code felt very messy.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Our task worker code, while cool, felt fragile.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So how does bull help? &lt;/p&gt;

&lt;p&gt;Task worker management:&lt;/p&gt;

&lt;p&gt;They have a solid interface for registering and running task workers. Making it much less work on our end to handle that part of the code. &lt;/p&gt;

&lt;p&gt;Keeping all the code contained:&lt;/p&gt;

&lt;p&gt;All pieces of the flow exist in the same codebase. Queue creation, event passing and task running without external setup for production.&lt;/p&gt;

&lt;p&gt;Less moving parts: &lt;/p&gt;

&lt;p&gt;In the previous iteration you had SNS, SQS, custom task handler code, and server code. The updated version uses just server code, Bull, and Redis. While only one less system is used in the updated version. We reap many benefits from the battle tested, and frankly much better, Bull code. &lt;/p&gt;

&lt;p&gt;Portability:&lt;/p&gt;

&lt;p&gt;Our task code now only relies on Redis. This makes it much easier to move to another cloud provider. No code changes required. &lt;/p&gt;

&lt;h2&gt;
  
  
  The How
&lt;/h2&gt;

&lt;p&gt;So it's pretty simple to implement. (You can refer to the &lt;a href="https://github.com/OptimalBits/bull"&gt;Bull documentation&lt;/a&gt; for most of this as well.)&lt;/p&gt;

&lt;h4&gt;
  
  
  Setting up queues
&lt;/h4&gt;

&lt;p&gt;We make a queue for each type of work we want to do. Such as emailing or sending texts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;emailQueue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;email&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="s1"&gt;redis://127.0.0.1:6379&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;smsQueue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sms&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="s1"&gt;redis://127.0.0.1:6379&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;p&gt;Then we have all our queues in an array so when an event comes in we can pass that to all our queues&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;queues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;emailQueue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;smsQueue&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;onEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&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="nx"&gt;queues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;)&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;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                &lt;span class="nx"&gt;name&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="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;And we can define our queue processors.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;emailQueue.process(emailTaskWorker);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Pretty simple stuff. &lt;/p&gt;

&lt;h3&gt;
  
  
  Final thoughts.
&lt;/h3&gt;

&lt;p&gt;Why pass events to all queues? It allows any any queue to decide what it wants to do. &lt;/p&gt;

&lt;p&gt;For example. If a user commented on a post, we might want to email them, text them, or generate a push notification. If in our code we did:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/posts/:id/comment, (req) =&amp;gt; {
    server.sendEmail(req)
    server.sendText(req)
    server.sendPush(req)
})
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;That would get really messy. Instead we just make a record of what event happened.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user-commented-on-post&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now any queue that cares about that event can do whatever it wants in response. It keeps our http handler code very clean as well.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Automating Lambda Deployment With Travis-CI</title>
      <dc:creator>Clay Murray</dc:creator>
      <pubDate>Fri, 27 Sep 2019 17:02:41 +0000</pubDate>
      <link>https://dev.to/powerc9000/automating-lambda-deployment-with-travis-ci-3171</link>
      <guid>https://dev.to/powerc9000/automating-lambda-deployment-with-travis-ci-3171</guid>
      <description>&lt;h2&gt;
  
  
  This is a post from my old blog. Info in here may be out of date or useless.
&lt;/h2&gt;

&lt;h3&gt;
  
  
  January 11, 2017
&lt;/h3&gt;

&lt;p&gt;I really hate doing things myself when I can have a computer do it for me.&lt;/p&gt;

&lt;p&gt;So In this post I will describe how I automate lambda deployment with travis-ci.&lt;/p&gt;

&lt;p&gt;Create a AWS role that has these permissions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="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="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&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="s2"&gt;"lambda:*"&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;"Resource"&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="s2"&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="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="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;I wanted to lock down the permissions more but you basically need them all.&lt;/p&gt;

&lt;p&gt;Get an access key for this role.&lt;/p&gt;

&lt;p&gt;Second add your repo to travis. It's easy I won't tell you how to do it.&lt;/p&gt;

&lt;p&gt;Now we create a &lt;code&gt;.travis.yml&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node_js&lt;/span&gt;
&lt;span class="na"&gt;node_js&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;4.3'&lt;/span&gt;
&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;global&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;AWS_ACCESS_KEY_ID=YOUR_ACCESS_KEY_ID&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;AWS_DEFAULT_REGION=us-east-1&lt;/span&gt;
&lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;only&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;master&lt;/span&gt;
&lt;span class="na"&gt;before_install&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pip install --user awscli&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;export PATH=$PATH:$HOME/.local/bin&lt;/span&gt;
&lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;make build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Here we add our AWS key id to the file. We'll also want to add our secret key but we want to encrypt it. Using the travis CLI&lt;/p&gt;

&lt;p&gt;&lt;code&gt;travis encrypt AWS_SECRET_ACCESS_KEY=YOUR_SECRET_ACCESS_KEY --add env.global&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We should be set on the travis side.&lt;/p&gt;

&lt;p&gt;Now onto building and uploading&lt;/p&gt;

&lt;p&gt;Here is my make file for building&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nl"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;build clean upload&lt;/span&gt;
&lt;span class="nl"&gt;build&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production
    zip &lt;span class="nt"&gt;-r&lt;/span&gt; code.zip &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-x&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;.git&lt;span class="k"&gt;*&lt;/span&gt;

&lt;span class="nl"&gt;clean&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; code.zip &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then &lt;/span&gt;&lt;span class="nb"&gt;rm &lt;/span&gt;code.zip&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="nl"&gt;upload&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;build&lt;/span&gt;
    ./upload.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;here is my upload.sh&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
aws lambda update-function-code &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--zip-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;fileb://code.zip &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;us-east-1 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--function-name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;NAME_OF_YOUR_LAMBDA
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now everytime you push to master it should update your lambda to the newest code!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Making Your Own 2.5D Renderer Thing In C++ For Fun </title>
      <dc:creator>Clay Murray</dc:creator>
      <pubDate>Fri, 27 Sep 2019 16:52:58 +0000</pubDate>
      <link>https://dev.to/powerc9000/making-your-own-2-5d-renderer-thing-in-c-for-fun-135b</link>
      <guid>https://dev.to/powerc9000/making-your-own-2-5d-renderer-thing-in-c-for-fun-135b</guid>
      <description>&lt;h2&gt;
  
  
  I had an old Blog and want to get the posts on here:
&lt;/h2&gt;

&lt;h3&gt;
  
  
  From February 2018:
&lt;/h3&gt;

&lt;p&gt;So I like to program. And I guess I like the idea of making a game. I really haven't finised many. &lt;br&gt;
Previously most of my attempts have been in Javacript using canvas &lt;a href="https://github.com/powerc9000/head-on.js"&gt;I made some library/engine thing&lt;/a&gt; even.&lt;br&gt;
I mean it isn't very good is it?&lt;/p&gt;

&lt;p&gt;Anyway, I think writing low level code makes you cool. And I want to be cool like &lt;a href="https://twitter.com/cmuratori"&gt;Casey Muratori&lt;/a&gt; or &lt;a href="https://twitter.com/Jonathan_Blow"&gt;Jonathan Blow&lt;/a&gt;&lt;br&gt;
I also think old school engines like &lt;a href="https://en.wikipedia.org/wiki/Build_(game_engine)"&gt;Build&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/Doom_engine"&gt;Doom&lt;/a&gt;, or &lt;a href="http://fabiensanglard.net/Game_Engine_Black_Book_Release_Date/index.php"&gt;Wolfenstien 3D&lt;/a&gt; are cool. &lt;br&gt;
Which makes me want to eschew all better ideas and do one similar to those.&lt;br&gt;
However, since I don't hate myself completely. I do use SDL for the platform layer.&lt;/p&gt;

&lt;p&gt;In the rest of this post I will go over some cool things I think I do.&lt;/p&gt;
&lt;h2&gt;
  
  
  Raycasting
&lt;/h2&gt;

&lt;p&gt;I don't really want to explain what ray casting is. I just want to say what &lt;em&gt;I&lt;/em&gt; do. It's probably stupid but I like it.&lt;/p&gt;

&lt;p&gt;Instead of just sending out a ray and following its path to see what it hits (I tried this). I do something a little different.&lt;br&gt;
I have a list of line segments that are the walls. They contain a start position, end position, texture index, and if they are transparent or not.&lt;br&gt;
For every ray I spit out (on each column of the screen) I also make a line segment the start is the player position and the end is some place like 2000 units away.&lt;br&gt;
Then every ray checks itself against every wall. &lt;br&gt;
Checking if it &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Hits the wall&lt;/li&gt;
&lt;li&gt;Where it hits the wall&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To know if a wall is already occulded I keep a buffer of every column and the largest height of the wall drawn there.&lt;br&gt;
If the height is smaller it doesn't get drawn. &lt;br&gt;
If the wall is transparent the drawing gets deffered until after all the rays have finished their checks then those walls are drawn back to front. Again checking the height buffer.&lt;/p&gt;

&lt;p&gt;In terms of rendering that's about what I have so far. Nothing too exciting but I think it's cool I even did it.&lt;br&gt;
I'm working on floors and ceilings now. It's pretty tough.&lt;/p&gt;
&lt;h2&gt;
  
  
  Hot reloading.
&lt;/h2&gt;

&lt;p&gt;I learned this one from Casey Muratori. On his stream &lt;a href="https://handmadehero.org/"&gt;Handmade Hero&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you separate your game into two parts. A platform layer and game layer. &lt;br&gt;
You can compile your platform layer as an EXE and your game layer as a DLL.&lt;br&gt;
As long as the platform layer holds the gamestate and passes it to the game layer.&lt;br&gt;
Anytime the DLL is rebuilt you can reload it in your platform layer and see your code changes on the fly without having to close and reopen the exe.&lt;/p&gt;

&lt;p&gt;Here is my shitty SDL code to do that.&lt;/p&gt;

&lt;p&gt;Oh and, we copy the DLL to a temp file so our game doesn't keep a file lock on the DLL and keep us from reloading it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight cpp"&gt;&lt;code&gt;        &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;LockFileName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"../build/lock.tmp"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;TempDLLName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"../build/temp.seville.dll"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;SourceDLLName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"../build/seville.dll"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="n"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SourceDLLName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;buff&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;modified&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;buff&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;st_mtime&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GameCodeDLL&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
                &lt;span class="n"&gt;SDL_UnloadObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GameCodeDLL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="n"&gt;UpdateAndRender&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="n"&gt;SDL_RWops&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SDL_RWFromFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LockFileName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"rb"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;stb_copyfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SourceDLLName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TempDLLName&lt;/span&gt;&lt;span class="p"&gt;)){&lt;/span&gt;
                    &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SDL_GetError&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;

                &lt;span class="n"&gt;GameCodeDLL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SDL_LoadObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TempDLLName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GameCodeDLL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;UpdateAndRender&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;game_update_and_render&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="n"&gt;SDL_LoadFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GameCodeDLL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"GameUpdateAndRender"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

                    &lt;span class="n"&gt;modified&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;buff&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;st_mtime&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SDL_GetError&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
                &lt;span class="n"&gt;SDL_RWclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UpdateAndRender&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;

            &lt;span class="n"&gt;UpdateAndRender&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Renderer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;keysHeld&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SDL_GetError&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&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;So that's where I'm at. &lt;/p&gt;

</description>
      <category>cpp</category>
    </item>
    <item>
      <title>How do you do language translations</title>
      <dc:creator>Clay Murray</dc:creator>
      <pubDate>Wed, 25 Sep 2019 02:13:00 +0000</pubDate>
      <link>https://dev.to/dropconfig/how-do-you-do-language-translations-2o90</link>
      <guid>https://dev.to/dropconfig/how-do-you-do-language-translations-2o90</guid>
      <description>&lt;p&gt;Having worked at a company that supported a large number of languages I always wondered how other developers managed theirs.&lt;/p&gt;

&lt;p&gt;The basic idea when I first started was: static JSON files with key value pairs. &lt;/p&gt;

&lt;p&gt;So English might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="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="nl"&gt;"welcome"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hello!"&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;p&gt;Then in the code you would reference the key to fill in the value.&lt;/p&gt;

&lt;p&gt;Something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Translate&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;welcome&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Translate&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;That's not a bad solution and the basic ideas were pretty alright. However, it has a few problems.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The people who can translate for you don't usually know JSON&lt;/li&gt;
&lt;li&gt;It doesn't provide much context on &lt;em&gt;what&lt;/em&gt; the translation should be&lt;/li&gt;
&lt;li&gt;It requires a code release every time you need to update a language or even fix a typo.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;#1 is not too hard. You can convert to CSV for loading into excel which is a tool most people know. You can still run into data conversion issues.&lt;/p&gt;

&lt;p&gt;#2 I never had a great solution for. Typically you would have to do a lot of work to provide screenshots and go back and forth  for them to have proper context. If you site was already live it was easier since they could just look at the live site.&lt;/p&gt;

&lt;p&gt;#3 Can be solved with hosting the JSON in another place such as S3 and have the app pull the data from there. But then you no longer have version control.&lt;/p&gt;

&lt;p&gt;Eventually I created an internal app to solve #1 and #3. It was a react app that used firebase as the storage. It integrated with our LDAP so we had simple SSO. &lt;br&gt;
We also had language and environment based permissions. You could say a user could only access French  in dev. &lt;br&gt;
It had a way to promote dev to production as well.&lt;/p&gt;

&lt;p&gt;Firebase was nice to use in that you can have direct JSON access URLs so the consuming app never had to install their bulky tools. (It wasn't important to have live updating of the translations)&lt;br&gt;
The app worked but was clunky, ugly, buggy and had little buy in from anyone but IT. I could think of many things I might improve on it.&lt;/p&gt;

&lt;p&gt;This was a bit of a ramble. But after talking about what I've done and worked on. What do you guys do?&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Write your own map filter and reduce</title>
      <dc:creator>Clay Murray</dc:creator>
      <pubDate>Tue, 16 Jul 2019 22:05:51 +0000</pubDate>
      <link>https://dev.to/powerc9000/write-your-own-map-filter-and-reduce-3mg9</link>
      <guid>https://dev.to/powerc9000/write-your-own-map-filter-and-reduce-3mg9</guid>
      <description>&lt;p&gt;So most of us know that these functions already exists.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;[].&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;[].&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;[].&lt;/span&gt;&lt;span class="nx"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;But how do they work internally? It's actually pretty easy. So let's just write them ourselves. Cool&lt;/p&gt;

&lt;h3&gt;
  
  
  Map
&lt;/h3&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cb&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
   &lt;span class="k"&gt;for&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;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&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;item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
      &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&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;Map is pretty simple. &lt;/p&gt;

&lt;h3&gt;
  
  
  Filter
&lt;/h3&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cb&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="k"&gt;for&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;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&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;item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&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;check&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
       &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;check&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
            &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
       &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&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;Also not too tough.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reduce
&lt;/h3&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;init&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;init&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;for&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;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&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;item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&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;Reduce is a little more involved but it isn't anything too crazy. We just make sure to return what they passed last iteration to the next iteration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final note:
&lt;/h2&gt;

&lt;p&gt;Interestingly enough. Reduce can be our base construct for all our other loops and we can use it to implement &lt;code&gt;map&lt;/code&gt; and &lt;code&gt;filter&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Map using reduce
&lt;/h3&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;carry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;carry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;carry&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;},&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;h3&gt;
  
  
  Filter using reduce
&lt;/h3&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;carry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;check&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;check&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
            &lt;span class="nx"&gt;carry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;carry&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;},&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;Anyway don't roll your own reduces and maps unless you have a good reason. But it is nice to know how things work at a lower level. That way you can better utilize them in the future.&lt;/p&gt;

&lt;p&gt;Thanks for reading. &lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
