Implement data storage

Implement a Storage interface to enable the Miro class to automatically manage access and refresh tokens.

Goal

This guide features two examples that demonstrate how to implement a custom Storage interface to add data storage functionality to the Miro class.
The default is a simple in-memory implementation.

🚧

For production deployments, we recommend using a custom implementation backed by a database.

Implement storage with a Redis backend

This example implements storage using:

Create a class to handle:

  • Connecting to a Redis instance.
  • Fetching state from Redis.
  • Saving state in Redis.
  • Deleting state from Redis.

In the example, the class name is RedisStorage:

const redis = require('redis')

class RedisStorage {
  // Initiate a connection to the Redis instance.
  // On subsequent calls, it returns the same Redis connection
  async _getClient() {
    if (!this.redisClient) {
      const client = redis.createClient()
      await client.connect()
      this.redisClient = client
    }
    return this.redisClient
  }

  // Return the state from Redis, if this data exists
  async get(userId) {
    const client = await this._getClient()
    const value = await client.get(userId.toString())
    if (!value) return undefined
    return JSON.parse(value)
  }

  // Store the state in Redis.
  // If the state is undefined, the corresponding Redis key is deleted
  async set(userId, state) {
    const client = await this._getClient()

    // Delete the state, if it's undefined
    if (!state) return await client.del(userId.toString())

    // Store the state in Redis
    await client.set(userId.toString(), JSON.stringify(state))
  }
}

To use the class, pass it as a parameter in the Miro constructor:

const miro = new Miro({
  storage: new RedisStorage(),
})

Implement storage with express-session

This example implements storage using:

  • The Express web framework.
  • The express-session middleware to enable storing Miro state information in the session storage.

The following code example:

  • Creates a new Miro instance for each request.
  • Loads the state of the current Miro instance from the session storage.
  • Saves the state of the current Miro instance to the session storage.
// Set up session middleware before: 'app.use(session({...}))'
app.use((req, res, next) => {
  // Create a separate 'Miro' instance for each request
  req.miro = new Miro({
    // Define the storage implementation inline
    storage: {
      // Load the 'Miro' state from the session object, if it exists
      get(_userId) {
        return req.session.state
      },

      // Store the 'Miro' state in the session object
      set(_userId, state) {
        req.session.state = state
      },
    },
  })

  next()
})

Instead of using a global miro variable, each request has a separate Miro instance associated with req.miro.
The client endpoints that interact with the Miro API use req.miro to handle session data.

Example:

app.get('/', async (req, res) => {
  if (!await req.miro.isAuthorized(req.session.id)) {
    res.redirect(req.miro.getAuthUrl())
    return
  }

  // ...
}

See also


What's next

Build a more complex app that implements OAuth authentication and uses the Express web framework.