Manage environment variables in a Sapper application

ยท 4 mins read ยท ย Svelteย Sapper

Updated: 16/5/2020

Sapper is a great option if we want to build fast and modern web applications - like the one you are using right now - by taking advantage of Svelte awesomeness.

Once we are done putting the main components in place, we definitely have to think about keeping our application as secure as possible by protecting sensitive information we are using.

These might be API tokens, keys, credentials or even configuration settings for third party services we are aiming to integrate. No-one wants to have such information hardcoded in the actual codebase and publish it on Github right?

Environment variables is the way to store and extract such sensitive keys from a common place.

Beware, anything that is stored in a JavaScript bundle is by no-mean super-secure since people who know where they should look for, can gain access to your sensitive keys. That said, you should include in your client-side code only keys that cannot harm you. For all the rest, better prefer a server-side solution!!

# Set things up

For our example here, let's say we need to implement an environment variable named NEWSLETTER_API_TOKEN which is required, so we can subscribe people to an external mailing list. Where should we place this variable then?

As a first step, we could create a .env file in the root of our repository and place it there.

For sure this file should be included in .gitignore at once, so we avoid committing it accidentally. This means that .env file will reside only in our machine for the time being.

# environment variables
NEWSLETTER_API_TOKEN=XXX

So now that we created .env file, we need to read NEWSLETTER_API_TOKEN variable somehow from there. How are we doing this then?

One of the best solutions available is dotenv package. We will install it, and then we are going to bootstrap it at the top of our ./src/server.js file before including anything else:

yarn add dotenv

And then in server.js file:

require('dotenv').config();

import sirv from 'sirv';
import polka from 'polka';
import compression from 'compression';
import * as sapper from '@sapper/server';

const { PORT, NODE_ENV, NEWSLETTER_API_TOKEN } = process.env;
const isDev = NODE_ENV === 'development';

const server = polka();

server
  .use(
    compression({ threshold: 0 }),
    sirv('static', { dev: isDev }),
    sapper.middleware(),
  )
  .listen(PORT, (err) => {
    if (err) console.log('error', err);
  });

It is important to bootstrap dotenv upfront, so we are totally certain the custom environment variable we added in .env file is read before our code runs. This means that when we destructure process.env, the NEWSLETTER_API_TOKEN variable will be available as wanted.

Ok, we got access to NEWSLETTER_API_TOKEN but how can we pass it to our Sapper application then? We can use session to accomplish this.

# Use Sapper session

We can easily pass key-value pairs to the application's session globally by using sapper.middleware.

This is why we bootstrapped dotenv in server.js file before launching polka server.

We can pass an option named session in sapper.middleware with our NEWSLETTER_API_TOKEN environment variable:

server
  .use(
    compression({ threshold: 0 }),
    sirv('static', { dev: isDev }),
    sapper.middleware({
      session: () => ({
        NEWSLETTER_API_TOKEN,
      }),
    }),
  )
  .listen(PORT, (err) => {
    if (err) console.log('error', err);
  });

So far so good but how we can access this variable from our Sapper pages? We can use the preload function that is provided for page components.

# Access session information while preloading a page component

Page components can have an optional function named preload that works similar to getInitialProps in Next.js and asyncData for Nuxt.js. This method lives in a context="module" script, and runs before the page component is created. It takes 2 arguments which are:

  • page
  • session

I bet you imagined already how we are going to get access to our environment variable:

<script context="module">
  export async function preload(page, session) {
    const { NEWSLETTER_API_TOKEN } = session;

    return { token: NEWSLETTER_API_TOKEN };
  }
</script>

<script>
	export let token;
</script>

Pretty straightforward right? We pulled NEWSLETTER_API_TOKEN from session argument, and then we returned it so that we can access it from our component's script as a token prop.

Cool, but how can we access NEWSLETTER_API_TOKEN from a non-page component?

# Subscribe to session store

We could use a prop but this component is a nested one and prop drilling is something we should avoid as much as possible especially when there are workarounds.

Actually, there is a workaround because session is a writable store. We can subscribe to it and listen to changes from interested components across our application no matter if they are pages components or not.

We can access session by importing stores from @sapper/app. This is a module generated by Sapper based on the shape of our application:

<script>
  import { stores } from '@sapper/app';

  let token = '';

  const { session } = stores();

  session.subscribe(value => {
    token = value.NEWSLETTER_API_TOKEN;
  });
</script>

Awesome!! We managed to subscribe to session store and access the environment variable in question.

One more thing we need to take care of, is to unsubscribe when our component unmounts. That way we will avoid potential memory leaks. The best way to achieve this is by using onDestroy lifecycle hook:

<script>
  import { onDestroy } from 'svelte';
  import { stores } from '@sapper/app';

  let token = '';

  const { session } = stores();

  const unsubscribe = session.subscribe(value => {
    token = value.NEWSLETTER_API_TOKEN;
  });

  onDestroy(unsubscribe);
</script>

Cool, but this is starting getting ugly since we need to subscribe and unsubscribe manually every time we need access to a store. Imagine how ugly this will get if we need to subscribe to multiple stores.

Svelte offers an outstanding auto-subscription utility for store variables by using the $ prefix. This is a reserved character and Svelte will prevent us from using it.

Let's refactor our component accordingly then:

<script>
  import { stores } from '@sapper/app';

  const { session } = stores();

  const { token } = $session;
</script>

Cool stuff!! Since we are done, it is about time to deploy our application.

# Production setup

Hmm, didn't we say that this variable is going to live in our machine only? So how this is going to be spotted in a production environment since .env is not going to be shipped alongside our application? We added .env file in .gitignore, right?

Normally, this is one of the responsibilities of a CI/CD process while preparing our application's bundle before deploying it to the production environment. This means that we have to store our environment variables in the CI/CD platform too. Then, the platform we are using, is responsible to make the mapping and include the keys in the production bundle while building it.

This application here is using Netlify so the only thing that needs to be done when adding a new variable is to add it also in Build & deploy > Environment > Environment variables section and then click the deploy button. Cheers!!

You can find a repository that includes everything mentioned above on Github ๐Ÿš€

Newsletter

Get notified about latest posts and updates once a week!!