Guide: creating a custom backend

Hosting an Alinea backend requires several services such as storing and retrieving drafts, publishing changes and authenticating users. We go through each of these and combine them into a handler which can be hosted on Node.js or serverless/edge Javascript runtimes. Let's start by adjusting the alinea.config.tsx file to include the backend setup.

Make sure you've exported the dashboard first

alinea.config.tsx
import {alinea} from 'alinea'
import {backend} from 'alinea/core'
import {passwordLess} from 'alinea/auth.passwordless'
import {configureBackend} from './alinea.server'
export const config = alinea.createConfig({
  backend: backend({
    auth: passwordLess
  }).configure(configureBackend),
  ...config
})

We set up the backend in a separate alinea.server.ts file that can be imported from the main alinea config. This file will hold all the configuration for the server side of the custom backend. When importing files that end with .server alinea makes sure they're not included in the client bundle, so your secrets are safe.

alinea.server.ts
import {PasswordLess} from 'alinea/auth.passwordless'
import {Backend, JsonLoader} from 'alinea/backend'
import {GithubData} from 'alinea/backend.github'
import {RedisDrafts} from 'alinea/backend.redis/RedisDrafts'
import {JWTPreviews} from 'alinea/backend/util/JWTPreviews'
import {backend} from 'alinea/core'
import Redis from 'ioredis'
import {createTransport} from 'nodemailer'

export const configureBackend = backend.configure<PasswordLess>(
  ({auth, config, createStore}) => {

    // The authentication service needs to link to your admin panel
    // to create magic links and email them
    const dashboardUrl = 'https://alinea.sh/admin.html'

    // Configure the server side of the passwordless service
    const passwordless = auth.configure({
      dashboardUrl,
      // This is the subject of the magic link mails
      subject: 'Login',
      // Send the mails from this address
      from: `"Alinea" <no-reply@alinea.sh>`,
      // We pass a nodemailer transporter so the service can send mails
      transporter: createTransport(
        // Pass all the required transport configuration options
        // See: http://nodemailer.com/smtp/
        {...smtpConfig} 
      ),
      // A secret is needed to sign the magic link tokens
      jwtSecret: process.env.JWT_SECRET!,
      // Email adresses pass through here while logging in,
      // return true when a user is valid and is allowed to log in
      async isUser(email: string) {
        return email.endsWith('@alinea.sh')
      }
    })

    // Create a drafts instance that will save and retrieve drafts from Redis
    const drafts = new RedisDrafts({
      client: new Redis(process.env.REDIS_DSN)
    })

    // Publish content changes to Github
    const data = new GithubData({
      config,
      loader: JsonLoader,
      // Pass a token which is authorized to read and write to the repository 
      githubAuthToken: process.env.GITHUB_TOKEN!,
      owner: 'alineacms',
      repo: 'alinea',
      branch: 'main',
      author: {
        name: 'My Name',
        email: 'me@example.com'
      }
    })
    
    return new Backend({
      dashboardUrl,
      auth: passwordless,
      config,
      createStore,
      drafts,
      target: data,
      media: data,
      previews: new JWTPreviews(process.env.JWT_SECRET!)
    })
  }
)

Authentication

The authentication service is the only one that provides not only a server implementation, but also a client part (the login screen). This is why the setup is included in the config file itself which is read by the client as well. We use the @alinea/auth.passwordless package. It can authenticate users with a magic link. It's ideal for serverless environments as it does not require a persistence layer. Install the backend and the nodemailer library which will handle sending emails.

npm install @alinea/auth.passwordless nodemailer

Drafts

In the example we sync drafts to and from a Redis store. You can create a free persistent Redis store at upstash.io to get started. Install the backend and the ioredis library which will handle the connection.

npm install @alinea/backend.redis ioredis

Another package is available to sync to Firebase.

If you're setting up Alinea to run fully inside the browser (like we did with our demo) drafts can be synced to IndexedDB.

Target and media

To publish content back to the git repository and store uploaded files we need a service that can handle that. Currently there is one package available to publish to Github hosted repositories. You'll have to create a token with read and write access to your repository.

npm install @alinea/backend.github