<?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>Forem: Bjørn Lindholm</title>
    <description>The latest articles on Forem by Bjørn Lindholm (@bjornlindholmdk).</description>
    <link>https://forem.com/bjornlindholmdk</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%2F186086%2F41089712-cc1c-4df9-b53c-7e40543916f9.jpg</url>
      <title>Forem: Bjørn Lindholm</title>
      <link>https://forem.com/bjornlindholmdk</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/bjornlindholmdk"/>
    <language>en</language>
    <item>
      <title>Create application menus with Vue templates in Electron</title>
      <dc:creator>Bjørn Lindholm</dc:creator>
      <pubDate>Thu, 05 Nov 2020 15:31:23 +0000</pubDate>
      <link>https://forem.com/bjornlindholmdk/create-application-menus-with-vue-templates-in-electron-3pff</link>
      <guid>https://forem.com/bjornlindholmdk/create-application-menus-with-vue-templates-in-electron-3pff</guid>
      <description>&lt;h1&gt;
  
  
  Create application menus with Vue templates in Electron
&lt;/h1&gt;

&lt;p&gt;For the last few months, I've been working on an app called &lt;a href="https://useserve.app/"&gt;Serve&lt;/a&gt;. It's an Electron app that makes it easy to set up local development environments for Laravel. &lt;/p&gt;

&lt;p&gt;In the latest release, I wanted to revamp the application menu. But I ran into some limitations with the existing Electron API, so I set out on a mission to figure out how to define the menu in a Vue component.&lt;/p&gt;

&lt;h2&gt;
  
  
  The main and renderer context
&lt;/h2&gt;

&lt;p&gt;If you are not familiar with Electron apps, I'll quickly run through the main architecture concepts.&lt;/p&gt;

&lt;p&gt;An Electron app has two processes: The main process and the renderer process. The main process is a node environment and has access to the filesystem. The renderer process is a browser environment and is responsible for handling the UI of the application.&lt;/p&gt;

&lt;p&gt;The processes can communicate with each other through what's called 'Inter-Process Communication' (IPC). IPC is essentially an event system that works across the processes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Electron's menu API.
&lt;/h2&gt;

&lt;p&gt;The existing API for creating application menus work in the main process. It involves building a template of JSON objects that represent submenus and menu items.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Menu } from 'electron'

Menu.setApplicationMenu(
    Menu.buildFromTemplate(
        {
            label: 'File',
            submenu: [
                {           
                    label: 'New project',
                    accelerator: 'CmdOrCtrl+n',
                    click: () =&amp;gt; console.log('New project')
                },
                {           
                    label: 'Import project',
                    accelerator: 'CmdOrCtrl+i',
                    click: () =&amp;gt; console.log('Import project')
                }
            ]
        }
    )
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The example above creates a 'File' submenu with two menu items.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problems with the existing API
&lt;/h2&gt;

&lt;p&gt;I found a couple of limitations with the existing API. First of all, it becomes quite a messy JSON tree when building out the entire menu structure. This JSON object is hard to read and understand easily.&lt;/p&gt;

&lt;p&gt;Secondly, Serve's renderer process is running a Vue application. But when the menu is defined in the main process, I can't call a method like `createProject' in the example above because that would be an action in the Vuex store.&lt;/p&gt;

&lt;p&gt;Lastly, I wanted to update the application menu based on where the user is. If the user has navigated into a project in the app, I want project-specific menu items like 'Start project' to be enabled. But if the user is not inside a project in the app, I want to disable those menu items. In other words, I was looking for a reactive menu. &lt;/p&gt;

&lt;h2&gt;
  
  
  Defining the API I wish I could use
&lt;/h2&gt;

&lt;p&gt;At this point, I decided to experiment with an alternative syntax. Ideally, I wanted to define the menu structure with Vue components instead of JSON objects. Here is the same menu as above using the syntax I wanted to use.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
&amp;lt;template&amp;gt;&lt;br&gt;
    &amp;lt;Menu&amp;gt;&lt;br&gt;
        &amp;lt;Submenu label="File"&amp;gt;&lt;br&gt;
            &amp;lt;MenuItem &lt;br&gt;
                accelerator="CmdOrCtrl+n" &lt;br&gt;
                @click="() =&amp;gt; console.log('New project')"&lt;br&gt;
            &amp;gt;&lt;br&gt;
                New project&lt;br&gt;
            &amp;lt;/MenuItem&amp;gt;&lt;br&gt;
            &amp;lt;MenuItem &lt;br&gt;
                accelerator="CmdOrCtrl+I" &lt;br&gt;
                @click="() =&amp;gt; console.log('Import project')"&lt;br&gt;
            &amp;gt;&lt;br&gt;
                Import project&lt;br&gt;
            &amp;lt;/MenuItem&amp;gt;&lt;br&gt;
        &amp;lt;/Submenu&amp;gt;&lt;br&gt;
    &amp;lt;/Menu&amp;gt;&lt;br&gt;
&amp;lt;/template&amp;gt;&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This syntax solves all the limitations I found. It's easier to scan and update the menu structure. It's defined in a Vue component, so it's automatically reactive. And since it's a Vue component, it lives in the renderer process and thus has access to the Vue context.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing the new API
&lt;/h2&gt;

&lt;p&gt;At this point, I had to try and implement the new syntax I had defined.&lt;/p&gt;

&lt;p&gt;The first step was figuring out how to tell the main process that the renderer process defines the menu.&lt;/p&gt;

&lt;p&gt;I created a &lt;code&gt;registerMenu&lt;/code&gt; method and called it in the main process.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
const registerMenu = () =&amp;gt; {&lt;br&gt;
    ipcMain.on('menu', (__, template = []) =&amp;gt; {&lt;br&gt;
        Menu.setApplicationMenu(&lt;br&gt;
            Menu.buildFromTemplate(templateWithListeners)&lt;br&gt;
        )&lt;br&gt;
    })&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;It defines a listener on the IPC channel 'menu'. It receives the template for the menu as a parameter in the callback. Lastly, it builds the application menu from the given template.&lt;/p&gt;

&lt;p&gt;In the renderer process, I created three Vue components: Menu, Submenu, and MenuItem.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Menu component
&lt;/h3&gt;

&lt;p&gt;The Menu component is responsible for controlling the state of the menu template and sending it over to the main process when it updates.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Fragment } from 'vue-fragment'

import EventBus from '@/menu/EventBus'

export default {
    components: {
        Fragment,
    },

    data() {
        return {
            template: {},
        }
    },

    mounted() {
        EventBus.$on('update-submenu', template =&amp;amp;gt; {
            this.template = {
                ...this.template,
                [template.id]: template,
            }
        })
    },

    watch: {
        template: {
            immediate: true,
            deep: true,
            handler() {
                window.ipc.send('menu', Object.values(this.template))
            },
        },
    },

    render(createElement) {
        return createElement(
            Fragment,
            this.$scopedSlots.default(),
        )
    },
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The component doesn't render any UI, but it returns the children of the component to execute them in the render method.&lt;/p&gt;

&lt;p&gt;The two most interesting things to look at is the 'template' watcher and the EventBus. The EventBus communicates between the Menu component and the Submenu components nested inside it. I didn't want to manually pass all events from the Submenu components up to the Menu components as that would clutter the API.&lt;/p&gt;

&lt;p&gt;The EventBus listen for events from the Submenu components. The submenu emits an event with the template for that submenu. In the Menu component, I update the state of the entire template.&lt;/p&gt;

&lt;p&gt;The 'template' watcher is responsible for sending the entire template tree to the main process when the template updates.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Submenu component
&lt;/h3&gt;

&lt;p&gt;The Submenu component is responsible for controlling all the menu items inside it and sending the state up to the Menu component when it updates.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { v4 as uuid } from 'uuid'
import { Fragment } from 'vue-fragment'

import EventBus from '@/menu/EventBus'

export default {
    components: {
        Fragment,
    },

    props: {
        label: String,
        role: {
            type: String,
            validator: role =&amp;amp;gt;
                [
                    'appMenu',
                    'fileMenu',
                    'editMenu',
                    'viewMenu',
                    'windowMenu',
                ].includes(role),
        },
    },

    data() {
        return {
            id: uuid(),
            submenu: {},
        }
    },

    computed: {
        template() {
            if (this.role) {
                return {
                    id: this.id,
                    role: this.role,
                }
            }

            return {
                id: this.id,
                label: this.label,
                submenu: Object.values(this.submenu),
            }
        },
    },

    mounted() {
        EventBus.$on('update-menuitem', template =&amp;amp;gt; {
            if (template.parentId !== this.id) {
                return
            }

            this.submenu = {
                ...this.submenu,
                [template.id]: template,
            }
        })
    },

    watch: {
        template: {
            immediate: true,
            deep: true,
            handler() {
                this.$nextTick(() =&amp;amp;gt; {
                    EventBus.$emit('update-submenu', this.template)
                })
            },
        },
    },

    render(createElement) {
        return createElement(
            Fragment,
            this.$scopedSlots.default(),
        )
    },
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;As with the Menu component, it doesn't render any UI, but the render method still needs to return all its children to execute the code in the MenuItem components.&lt;/p&gt;

&lt;p&gt;The component uses the EventBus to communicate with both the Menu component and the MenuItem components. It listens for updates in MenuItem components. &lt;/p&gt;

&lt;p&gt;Since the EventBus sends events to all Submenu components, it needs a unique id to control whether the menu item that emits the event is inside this specific submenu. Otherwise, all the submenus would contain all the menu items.&lt;/p&gt;

&lt;h3&gt;
  
  
  The MenuItem component
&lt;/h3&gt;

&lt;p&gt;The MenuItem component is responsible for controlling the state of a single menu item object and emit it up the tree when it updates.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { v4 as uuid } from 'uuid'

import EventBus from '@/menu/EventBus'

export default {
    props: {
        role: {
            type: String,
            validator: role =&amp;amp;gt;
                [
                    'undo',
                    'redo',
                    'cut',
                    'copy',
                    'paste',
                                            // ...
                ].includes(role),
        },
        type: {
            type: String,
            default: 'normal',
        },
        sublabel: String,
        toolTip: String,
        accelerator: String,
        visible: {
            type: Boolean,
            default: true,
        },
        enabled: {
            type: Boolean,
            default: true,
        },
        checked: {
            type: Boolean,
            default: false,
        },
    },

    data() {
        return {
            id: uuid(),
        }
    },

    computed: {
        template() {
            return {
                id: this.id,
                role: this.role,
                type: this.type,
                sublabel: this.sublabel,
                toolTip: this.toolTip,
                accelerator: this.accelerator,
                visible: this.visible,
                enabled: this.enabled,
                checked: this.checked,
                label: return this.$scopedSlots.default()[0].text.trim(),
            }
        },
    },

    watch: {
        template: {
            immediate: true,
            handler() {
                EventBus.$emit('update-menuitem', {
                    ...JSON.parse(JSON.stringify(this.template)),
                    click: () =&amp;amp;gt; this.$emit('click'),
                    parentId: this.$parent.template.id,
                })
            },
        },
    },

    render() {
        return null
    },
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The MenuItem doesn't render any UI either. Therefore it can simply return null.&lt;/p&gt;

&lt;p&gt;The component receives many props that correspond to the options you can give a menu item in the &lt;a href="https://www.electronjs.org/docs/api/menu-item"&gt;existing api&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;An example I used earlier is the &lt;code&gt;enabled&lt;/code&gt; prop that can control whether the menu item is active. &lt;/p&gt;

&lt;p&gt;When the template is updated, it emits an event to all the Submenu components with the template and parent id.&lt;/p&gt;

&lt;h3&gt;
  
  
  Putting it all together
&lt;/h3&gt;

&lt;p&gt;With all the individual pieces created, it was time to put it all together. I made an AppMenu component and included it in &lt;code&gt;App.vue&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
&amp;lt;template&amp;gt;&lt;br&gt;
    &amp;lt;Menu&amp;gt;&lt;br&gt;
        &amp;lt;Submenu label="File"&amp;gt;&lt;br&gt;
            &amp;lt;MenuItem &lt;br&gt;
                accelerator="CmdOrCtrl+n" &lt;br&gt;
                @click="() =&amp;gt; console.log('New project')"&lt;br&gt;
            &amp;gt;&lt;br&gt;
                New project&lt;br&gt;
            &amp;lt;/MenuItem&amp;gt;&lt;br&gt;
            &amp;lt;MenuItem &lt;br&gt;
                accelerator="CmdOrCtrl+I" &lt;br&gt;
                @click="() =&amp;gt; console.log('Import project')"&lt;br&gt;
            &amp;gt;&lt;br&gt;
                Import project&lt;br&gt;
            &amp;lt;/MenuItem&amp;gt;&lt;br&gt;
        &amp;lt;/Submenu&amp;gt;&lt;br&gt;
    &amp;lt;/Menu&amp;gt;&lt;br&gt;
&amp;lt;/template&amp;gt;&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;At this point, I discovered a pretty big issue, though. None of the click event handlers worked.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dealing with click handlers
&lt;/h3&gt;

&lt;p&gt;After some debugging, I found the issue. IPC communication is event-based, and it's not possible to include a JS function in the event object. But that's what I was doing in the template of a menu item:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
{&lt;br&gt;
    label: 'New project',&lt;br&gt;
    click: () =&amp;gt; this.$emit('click'),&lt;br&gt;
    // ...&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;The solution was hacky but worked. I omitted the click handler from the menu item objects. In the &lt;code&gt;registerMenu&lt;/code&gt; function, I attached a click handler to all menu items.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
export const registerMenus = win =&amp;gt; {&lt;br&gt;
    ipcMain.on('menu', (__, template = []) =&amp;gt; {&lt;br&gt;
        let templateWithListeners = template.map(group =&amp;gt; {&lt;br&gt;
            return {&lt;br&gt;
                ...group,&lt;br&gt;
                submenu: group.submenu.map(item =&amp;gt; {&lt;br&gt;
                    return {&lt;br&gt;
                        ...item,&lt;br&gt;
                        click: () =&amp;gt;&lt;br&gt;
                            win.webContents.send('menu', { id: item.id }),&lt;br&gt;
                    }&lt;br&gt;
                }),&lt;br&gt;
            }&lt;br&gt;
        })&lt;br&gt;
        Menu.setApplicationMenu(Menu.buildFromTemplate(templateWithListeners))&lt;br&gt;
    })&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The click handler sends an event on the &lt;code&gt;menu&lt;/code&gt; IPC channel. In AppMenu, I receive the event from the main event and sends another event using the EventBus.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
window.ipc.receive('menu', response =&amp;gt; {&lt;br&gt;
    EventBus.$emit('clicked', response.id)&lt;br&gt;
})&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Lastly, in MenuItem, I can listen for the event on the EventBus and emit a click event.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`&lt;br&gt;
EventBus.$on('clicked', id =&amp;gt; {&lt;br&gt;
    if (id !== this.id) {&lt;br&gt;
        return&lt;br&gt;
    }&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;this.click()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;})&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;The code examples in this article are simplified a bit. You can view the menu I created for Serve &lt;a href="https://github.com/BjornDCode/serve/blob/master/src/components/AppMenu.vue"&gt;here&lt;/a&gt; and view the source code for the menu &lt;a href="https://github.com/BjornDCode/serve/tree/master/src/menu"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;All in all, I'm happy with the outcome. My menu is now easier to maintain, it's reactive, and it simplified the rest of the app because I can call Vuex actions directly from the menu.&lt;/p&gt;

&lt;p&gt;If you are a Laravel developer, you should check out Serve. It automatically manages PHP, Node, databases, and all that kind of stuff for you. If you are not a Laravel developer, keep an eye out because Serve will support other frameworks and languages in the future.&lt;/p&gt;

</description>
      <category>electron</category>
      <category>javascript</category>
      <category>vue</category>
    </item>
    <item>
      <title>Storing tokens in single-page applications</title>
      <dc:creator>Bjørn Lindholm</dc:creator>
      <pubDate>Wed, 26 Aug 2020 14:19:22 +0000</pubDate>
      <link>https://forem.com/bjornlindholmdk/storing-tokens-in-single-page-applications-322k</link>
      <guid>https://forem.com/bjornlindholmdk/storing-tokens-in-single-page-applications-322k</guid>
      <description>&lt;p&gt;Single-page applications that use tokens to authenticate users need to implement a strategy for storing the tokens securely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't use localStorage
&lt;/h2&gt;

&lt;p&gt;Tokens should not be stored in &lt;code&gt;localStorage&lt;/code&gt; or &lt;code&gt;sessionStorage&lt;/code&gt;. These data stores are accessible from any JavaScript code running on the page. Storing tokens in &lt;code&gt;localStorage&lt;/code&gt; makes your application vulnerable to XSS attacks.&lt;/p&gt;

&lt;p&gt;You might think you control all the JavaScript on a page, but that's not necessarily true. A dependency of a dependency, a 3rd party tracking script, or a chrome plugin are all examples of code that could be malicious without your knowledge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't use browser cookies
&lt;/h2&gt;

&lt;p&gt;Tokens should not be stored in cookies created with JavaScript. JavaScript cookies, similarly to &lt;code&gt;localStorage&lt;/code&gt;, can be read by other JavaScript code.&lt;/p&gt;

&lt;p&gt;Malicious code running on the client could access the token and make requests on behalf of the user.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do use memory
&lt;/h2&gt;

&lt;p&gt;Storing a token in memory is better than saving a token in &lt;code&gt;localStorage&lt;/code&gt;, as long as it isn't stored in variable accessible by the global scope.&lt;/p&gt;

&lt;p&gt;The problem with storing tokens in memory is that the storage isn't persistent when a user refreshes the page or opens your application in a new browser tab.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do use HTTP cookies
&lt;/h2&gt;

&lt;p&gt;Tokens can be stored in cookies created by the server as long as it has the correct security attributes.&lt;/p&gt;

&lt;p&gt;A cookie storing a token should have the &lt;code&gt;HttpOnly&lt;/code&gt; attribute. This attribute ensures it cannot be read with JavaScript, thus protecting against XSS attacks.&lt;/p&gt;

&lt;p&gt;The cookie should also have the &lt;code&gt;Secure&lt;/code&gt; attribute. This attribute ensures the cookie can only be set and read on HTTPS connections. Using an encrypted connection protects against man-in-the-middle attacks.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Domain&lt;/code&gt; attribute should be used to ensure that the cookie is only returned to the server of the application.&lt;/p&gt;

&lt;p&gt;Lastly, the &lt;code&gt;SameSite&lt;/code&gt; attribute should be either &lt;code&gt;Strict&lt;/code&gt; or &lt;code&gt;Lax&lt;/code&gt;. This attribute ensures that only the server can store the cookie, which protects against CSRF attacks.&lt;/p&gt;

&lt;p&gt;For this method to work, the client and server applications must be hosted on the same domain.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Tired of handling authentication manually? I'm creating starter kits for Laravel and Vue/React for different authentication methods. Check out &lt;a href="https://usetitanium.com/"&gt;Titanium&lt;/a&gt; to stay updated!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>vue</category>
      <category>react</category>
      <category>security</category>
    </item>
    <item>
      <title>Using Airtable as a backend</title>
      <dc:creator>Bjørn Lindholm</dc:creator>
      <pubDate>Sun, 22 Sep 2019 10:16:09 +0000</pubDate>
      <link>https://forem.com/bjornlindholmdk/using-airtable-as-a-backend-5079</link>
      <guid>https://forem.com/bjornlindholmdk/using-airtable-as-a-backend-5079</guid>
      <description>&lt;h1&gt;
  
  
  Using Airtable as a backend
&lt;/h1&gt;

&lt;p&gt;I recently launched the first version of &lt;a href="http://conferencehq.io" rel="noopener noreferrer"&gt;Conference HQ&lt;/a&gt;. An interesting part of the project is the backend which is powered by Airtable. I originally built my own API in Laravel but I had dropped the project at that point. I'll come back to that later.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Airtable
&lt;/h2&gt;

&lt;p&gt;Airtable is a mix between a spreadsheet and a database. It's simpler than Firebase but has some convenient features like asset handling that makes it better for managing content than Google Sheets.&lt;/p&gt;

&lt;p&gt;At the core of Airtable are bases. It's the equivalent of a database or a spreadsheet. Inside the base you have tables. Inside tables is where the content lives. These are called records.   &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fa8mom8iuq0m0ai0wdnhj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fa8mom8iuq0m0ai0wdnhj.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Where Airtable really shines are the field types you can have for each column in a table. Standard options like text, number and date fields are available. But more advanced fields are available as well. &lt;/p&gt;

&lt;p&gt;You can have a email, phone number or url field if you are storing data about customers. Rating, duration, barcode, select and attachment fields are also available. The attachment field can be used to store images or files to display or download.&lt;/p&gt;

&lt;p&gt;Lastly you can link to other records or read a value from another record and use it in a formula. &lt;/p&gt;

&lt;h2&gt;
  
  
  Why I chose Airtable for Conference HQ
&lt;/h2&gt;

&lt;p&gt;I had originally built a Laravel API as the backend. But for various reasons I dropped the project for a while and when I revived it I wanted a simpler solution.&lt;/p&gt;

&lt;p&gt;Airtable was the right solution for that. At it's core, Conference HQ is a list of conferences. A custom API solution was overkill and instead of spending time maintaining the API, Airtable allowed me to focus on other things. I would estimate that I've spend around 3 hours setting up the base itself. &lt;/p&gt;

&lt;p&gt;It's easy to set up, especially if you have experience working with databases. The asset handling is convenient for Conference HQ because I store a logo for each conference. It's also nice not having to worry about setting up and maintaining the server.&lt;/p&gt;

&lt;p&gt;The free plan is sufficient for most small sites.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to use Airtable
&lt;/h2&gt;

&lt;p&gt;I would recommend using Airtable for list or directory sites. This could be a job board like &lt;a href="https://cryptocurrencyjobs.co/" rel="noopener noreferrer"&gt;Cryptocurrency Jobs&lt;/a&gt;, a database like &lt;a href="https://nomadlist.com" rel="noopener noreferrer"&gt;Nomad List&lt;/a&gt; or a directory like &lt;a href="https://madewithvuejs.com" rel="noopener noreferrer"&gt;Made with Vue.js&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you plan on adding other content like blog posts, I would still recommend using Airtable for the list part. Using a static site generator like Gridsome or Gatsby will allow you to fetch content from difference sources.&lt;/p&gt;

&lt;h2&gt;
  
  
  Working with Airtable's API
&lt;/h2&gt;

&lt;p&gt;After creating a base on Airtable, they auto generate API documentation for that base. This adapt as you add or remove tables or fields from the base.&lt;/p&gt;

&lt;p&gt;There is an official &lt;a href="https://github.com/Airtable/airtable.js" rel="noopener noreferrer"&gt;JS client&lt;/a&gt; that can be utilised. If you are using another language to power the site you can hit the endpoints directly.&lt;/p&gt;

&lt;p&gt;Authentication is done with a token. Unfortunately it's not possible to make endpoints public. This means the API has to be called from a server, to avoid the token being public. This is the reason I chose to use a static site generator over a SSR framework like Nuxt.&lt;/p&gt;

&lt;p&gt;The API has a rate limit of 5 requests per second. Unless you have a site with a lot of traffic this should be sufficient. Using a static site generator mitigates this problem as it fetches all data when the site is deployed.&lt;/p&gt;

&lt;p&gt;Working with the Airtable API won't be the same as working with a custom API. You don't have as much freedom when it comes to validation and connecting models through relationships. &lt;/p&gt;

&lt;p&gt;An example is that you can't have a 1 to 1 or a 1 to many relationship. Relationships are always many to many. Another example is that you can't automatically generate slugs. I have set up a formula that removes special characters from the conference names and turn it into dashes or an empty string.&lt;/p&gt;

&lt;p&gt;But I haven't found anything that I couldn't work around yet and considering the time saved by using Airtable I think it's worth it for simple sites.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frontend
&lt;/h2&gt;

&lt;p&gt;Because of the API authentication problem, I chose &lt;a href="https://gridsome.org/" rel="noopener noreferrer"&gt;Gridsome&lt;/a&gt; as a static site generator.&lt;/p&gt;

&lt;p&gt;Originally I wanted to use &lt;a href="https://nuxtjs.org/" rel="noopener noreferrer"&gt;Nuxt&lt;/a&gt; and use server side rendering. But the way data Nuxt fetches data from an API is through a method called &lt;a href="https://nuxtjs.org/guide/async-data" rel="noopener noreferrer"&gt;asyncData&lt;/a&gt;. This method fetches data on the server the first time it's run, but when the user navigates to other routes it will be called from the client. This would expose my Airtable API key.&lt;/p&gt;

&lt;p&gt;Gridsome fetches all data when I run the &lt;code&gt;gridsome build&lt;/code&gt; command. I do that when I deploy the site. It then generates every page into a static html file which is served. &lt;/p&gt;

&lt;p&gt;An advantage of using Gridsome (or &lt;a href="https://www.gatsbyjs.org/" rel="noopener noreferrer"&gt;Gatsby&lt;/a&gt; for React), is that you can have multiple data sources. That means I can create a blog using Contentful or Netlify CMS and fetch the data through Gridsome as well. &lt;/p&gt;

&lt;p&gt;Gridsome is still young and I ran into some caveats. The biggest one is search. This feature hasn't been launched on Conference HQ yet, but I'm currently implementing it. &lt;/p&gt;

&lt;p&gt;There are some search capabilities available in Gridsome but they are limited. Instead I'm using &lt;a href="https://github.com/nextapps-de/flexsearch" rel="noopener noreferrer"&gt;FlexSearch&lt;/a&gt;. I'll write another article with more details when the search and filter features have been shipped. &lt;/p&gt;

&lt;h2&gt;
  
  
  Handling forms
&lt;/h2&gt;

&lt;p&gt;A feature I want to add to the site in the future is the ability for users to add conferences. For this I've come up with two solutions.&lt;/p&gt;

&lt;p&gt;I can either create a &lt;a href="https://www.typeform.com/" rel="noopener noreferrer"&gt;Typeform&lt;/a&gt; or use &lt;a href="https://www.netlify.com/products/forms/" rel="noopener noreferrer"&gt;Netlify Forms&lt;/a&gt;. Since the site is already hosted on Netlify I'll most likely go with that solution.&lt;/p&gt;

&lt;p&gt;In order to store the fetched content in Airtable I'll use Zapier. Zapier has integrations with both Netlify and Typeform as well as Airtable.&lt;/p&gt;

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

&lt;p&gt;For a long time I've had a habit of trying to over-engineer my projects. This has always led me down a path where I spent a lot of time writing code that never see the light of day. Using Airtable saved me weeks of work creating a backend and allowed me to spend that time on adding features and content to the site instead. Airtable is not a viable solution for any site but if you want to quickly launch a project or test out an idea it's really solid. &lt;/p&gt;

</description>
      <category>javascript</category>
      <category>vue</category>
      <category>sideprojects</category>
    </item>
    <item>
      <title>3 small things I’ve learned about SVG’s</title>
      <dc:creator>Bjørn Lindholm</dc:creator>
      <pubDate>Mon, 22 Jul 2019 17:28:58 +0000</pubDate>
      <link>https://forem.com/bjornlindholmdk/3-small-things-i-ve-learned-about-svg-s-eop</link>
      <guid>https://forem.com/bjornlindholmdk/3-small-things-i-ve-learned-about-svg-s-eop</guid>
      <description>&lt;p&gt;SVG’s are great. And not super complex. But over the last couple of years I’ve picked up a few tips that made my SVG’s look better.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Put all icons on a shared size background
&lt;/h2&gt;

&lt;p&gt;Chances are that you’ve downloaded an icon pack like Material Icons at some point. If you ever open one of these icons in Sketch you’ll see that it sits on a 24px by 24px background.&lt;/p&gt;

&lt;p&gt;I used to think this was super annoying. It caused problems with alignment and I didn’t like this implicit box around the icon. But it turns out there is a meaning behind the madness.&lt;/p&gt;

&lt;p&gt;SVG icons can have many different shapes. Some are wide, some are tall. By using a common size background it makes it easy to resize the icons without any issues. It ensures that all icons are scaled properly. &lt;/p&gt;

&lt;p&gt;Let’s say that your design says that all icons should be 20px wide. The first icon you use is wide and it looks fine in the design. But the next icon is more tall than wide, and now you run into problems. The tall icon will be 20px wide but a lot taller than the other icon. It will scale differently and look bigger.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F4tunltatdjfn5wk0t92r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F4tunltatdjfn5wk0t92r.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Convert text to outlines
&lt;/h2&gt;

&lt;p&gt;You’ve just created a new logo for your side project. It has the company name and a custom shape next to it. It’s super cool. You export it as an SVG and put it on your website. But it doesn’t look right. The brand new font you used is gone and it looks like Times New Roman. &lt;/p&gt;

&lt;p&gt;I’ve made this mistake countless times. The problem is that the SVG uses a text element. This text element is using a font. This problem will never occur if you use a standard web font. But if you use a custom font and don’t import it on the website then it won’t work. &lt;/p&gt;

&lt;p&gt;The danger with this is that you might not notice until you deploy to a server. Since you designed the logo on your own machine you most likely have that font installed locally. But it’s not installed on the server.&lt;/p&gt;

&lt;p&gt;There are several ways to avoid this issue. The simplest one is including the font on the website. This can be a lot of extra bandwidth and if you aren’t using the font in other parts of the website I don’t recommend this approach.&lt;/p&gt;

&lt;p&gt;A better approach in my opinion is converting the text to a path. This can be done in any vector design app like Illustrator or Sketch. &lt;/p&gt;

&lt;p&gt;Just make sure to keep a copy of the logo with the original text in case you ever have to adjust the text styles or the text itself.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fur0p4j6fyshi1yygq7z1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fur0p4j6fyshi1yygq7z1.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Use icons from the same icon pack
&lt;/h2&gt;

&lt;p&gt;This is more of a design tip than technical advice. In the past I used to go on FlatIcon and find all icons individually. The problem with this is that they are all designed in different styles. They vary in stroke, shape and other small design elements. This leads to an inconsistent and confusing design. &lt;/p&gt;

&lt;p&gt;Now I always pick an icon pack and make sure to only use icons from that pack. This means sometimes compromising about finding the perfect icon if it’s not already in that pack. I think this trade off is worth it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fyzicypcnfx8ayzzzvx66.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fyzicypcnfx8ayzzzvx66.png"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;These are some of the small gotchas I’ve learned about SVG’s in the last few years. It might seem obvious to you but I never thought about any of these things before they became an issue.&lt;/p&gt;

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