Search code examples
pm2nuxt3.jsdexie

Using different Dexie Cloud configs for different environments with Nuxt3


I'm writing an app using Nuxt3 and Dexie Cloud, deploying with PM2 to my dev Ubuntu webserver on my LAN. All was well, until I decided to to add another deployment, which I would treat as "production", to mimic the end user's setup. I won't be able to just delete databases and tables there, so I am preparing to run it on a separate database.

I planned changing the db name in the instantiation to a variable (new Dexie(bdVar)) and the same for the url in db.cloud.configure.

And that's where things blew up... Cause... Where do I get the values for those variables from? .env doesn't work as I thought it would (Node, runtime, etc). Nuxt's runtimceConfig... well, still trying to wrap my head around that.

Let me give an overview of what I have:

// db/index.js
import { Dexie } from 'dexie'
import { dexieCloud } from 'dexie-cloud-addon'

export const db = new Dexie(dexieDb***, {addons: [dexieCloud]})

db.version(1).stores({
  // table definitions
})

db.cloud.configure({
  databaseUrl: dexieDbUrl***,
  requireAuth: true
})
*** means it works fine when string provided

Then I have a few other db files, each with related queries/transactions

// db/items.dexie.js
import { db } from '~/db/index.js'

export const fetchItems = async (realmId) => {
  const result = await db.items.where('realmId').equals(realmId).sortBy('last_update')
  return result
}
...

Then I have my Pinia stores, items store, shortened, as example

// stores/items.js 
import { fetchItems } from '~/db/items.dexie.js'

export const useItemStore = defineStore('items', () => {
  let items = ref([])

  const getItems = async () => {
    if (items.value.length === 0) items.value = await fetchItems(currentSpace.value.realmId)
    return items
  }

  ...

  return {
    items,
    getItems,
    ...
  }
})

A simple example of a page:

// pages/items.vue
<script setup>
const { items } = storeToRefs(useItemStore())

onMounted(async () => {
  if (items.value.length < 1) items.value = await useItemStore().getItems()
})
</script>

<template>
  <div v-for="item in items" :key="item.id">
    {{ item.name }}
  </div>
</template>

I tried adding the runtimeConfig in the nuxt.conf.ts:

export default defineNuxtConfig({
  runtimeConfig: {
    dexieDb: '', // .env entry: NUXT_DEXIE_DB
    dexieDbUrl: '', // .env entry: NUXT_DEXIE_DB_URL
  },
})

The next logical step (in my head, of course) would be to now access the runtimeConfig in db/index.js:

// db/index.js
import ...

const runtime = useRuntimeConfig()

export const db = new Dexie(runtime.dexieDb, {addons: [dexieCloud]})
...

The 500 error does clearly explain that this won't work: A composable that requires access to the Nuxt instance was called outside of a plugin, Nuxt hook, Nuxt middleware, or Vue setup function.

And this is where I'm stuck at the moment. How do I make the Dexie db available, while using variables that will have different values, depending on the environment, while using "a plugin, Nuxt hook, Nuxt middleware, or Vue setup function"?

Note: Some of the information I need to have as variables is sensitive.


Solution

  • I went to bed with this question in my head. Woke up with a "solution". Although there might be a more elegant way of doing this, this is what I came up with:

    First, I moved db/index.js into a plugin

    // plugins/dexie.js
    import { Dexie } from 'dexie'
    import { dexieCloud } from 'dexie-cloud-addon'
    
    export default defineNuxtPlugin(nuxtApp => {
      const runtimeConfig = useRuntimeConfig()
      
      const db = new Dexie(runtimeConfig.public.dexieDb, {addons: [dexieCloud]})
    
      db.version(1).stores({
        // table definitions
      })
    
      db.cloud.configure({
        databaseUrl: runtimeConfig.public.dexieDbUrl,
        requireAuth: true
      })
    
      nuxtApp.db = db
    })
    

    Moved the variables to the public runtimeConfig

    // nuxt.config.ts
    export default defineNuxtConfig({
      runtimeConfig: {
        public: {
          dexieDb: '', // env var NUXT_PUBLIC_DEXIE_DB
          dexieDbUrl: '', // env var NUXT_PUBLIC_DEXIE_DB_URL
        },
      },
      ...
    })
    

    Then I changed the db methods to expect a reference to the db

    // db/items.dexie.js
    export const fetchItems = async (db, realmId) => {
      return await db.items.where('realmId').equals(realmId).sortBy('last_update')
    }
    ...
    

    In the stores I get the db from the NuxtApp, and pass it along

    // stores/items.js 
    import { fetchItems } from '~/db/items.dexie.js'
    
    export const useItemStore = defineStore('items', () => {
      const { db } = useNuxtApp()
    
      let items = ref([])
    
      const getItems = async () => {
        if (items.value.length === 0) items.value = await fetchItems(db, currentSpace.value.realmId)
        return items
      }
    
      ...
    
      return {
        items,
        getItems,
        ...
      }
    })
    

    This worked great in dev! It uses the .env in the project root.

    For "production" I needed to update the ecosystem.config.cjs, adding the env variables (I'm using PM2)

    module.exports = {
      apps: [{
          name: 'InstanceName',
          port: '3005',
          exec_mode: 'cluster',
          instances: '2',
          script: './.output/server/index.mjs',
          env: {
            'NUXT_PUBLIC_DEXIE_DB': 'home-db',
            'NUXT_PUBLIC_DEXIE_DB_URL': 'https://xxxxxxxxx.dexie.cloud'
          },
        }]
    }
    

    Now I have my local dev, and two instances on my LAN webserver: One "dev" (using the same db as my local, so I can delete and update. and mess around as much as I want) and one "production" (using a separate db, as it would be in the actual production.

    I hope this helps someone who gets stuck with this...