maxibase

Nuxt layer with auth, files, logs and backups
917
Install
npm install maxibase
Latest Version:1.5.0
Vue:3
Last Updated:Oct 23, 2026
Links: GitHub  ·  npm
Maintainer: maxirozay

Nuxt base

A Nuxt layer with auth, files, logs and backups, meant to be extended by other projects.

Dev

Setup your env file by editing the .env.example file and rename it to .env.

Email customisation

Use the route http://localhost:3000/dev/email/[templateId] to see the email while you edit. You can put the locale or any parameter in the query.

Microsoft sign-in

In your Azure app registration, go to Token configuration → Add optional claim → ID and add email and xms_edov. Without them every Microsoft sign-in is refused.

A Microsoft account signs in to the user with the same email, so the email is only trusted when xms_edov says its domain is verified in the account's tenant. Any tenant can write any address in a user's mail, so trusting it unverified lets them sign in as anyone.

The tenant defaults to organizations: any work or school account, no personal ones. Set NUXT_OAUTH_MICROSOFT_TENANT=common to also accept personal accounts.

/auth/microsoft signs in with the Microsoft account already signed in, link to /auth/microsoft?prompt=select_account to let the user pick another one.

Deployment

On your server

Create the Docker shared-network (docker network create shared-network || true).

Start the postgres and drizzle-gateway dockers from the remote-db folder to manage your DB. Use the addDB script to create a new DB (./addDB.sh {db-name} {username} {password}).

Create a folder for your project where you will deploy the website and add your env file with the production values.

Setup nginx serve your app and your public files folder or s3 public url.

Serve s3 folder
location /files/ {
    rewrite ^/files/(.*) /object/v1/AUTH_123/bucket/$1 break;

    # 2. Proxy the S3 endpoint
    proxy_pass https://s3.pub2.infomaniak.cloud;

    # 3. Essential headers for SSL S3 connection
    proxy_set_header Host s3.pub2.infomaniak.cloud;
    proxy_ssl_server_name on;

    # Optional: Hide S3 headers for security
    proxy_hide_header x-amz-request-id;
    proxy_hide_header x-amz-id-2;

    add_header X-Content-Type-Options nosniff always;
    add_header Content-Security-Policy sandbox always;
}
Server server folder
location /files/ {
    alias /home/debian/project/volumes/files/public/;
    add_header X-Content-Type-Options nosniff always;
    add_header Content-Security-Policy sandbox always;
}

On your local machine

Run pnpm prod to deploy the website. To deploy with another env file, run pnpm prod -e .env.{name}.

It builds with the Dockerfile and compose.yaml at the root of your project, or the package's ones in docker/ when you have none. To customise them, copy them from node_modules/maxibase/docker, along with Dockerfile.dockerignore renamed to .dockerignore.

Each build is tagged {utc-timestamp}-{commit} and the last 5 stay on the server.

Roll back with pnpm rollback (the version before the live one), pnpm rollback {tag}, or list what is available with pnpm rollback --list. Rolling back does not undo migrations.

Push your migration through SSH with pnpm db:push-prod.

In a project that uses the layer, these scripts run from the package, see Scripts.

Run one instance

Rate limit counters and the auth storage that holds OTPs and email-change codes both live in the process memory (nitro.storage.auth is the memory driver). A second container, or node in cluster mode, gets its own copy: rate limits are multiplied by the number of instances, and an OTP verified on the instance that did not issue it fails.

Scale up only after pointing both at shared storage — swap the auth storage driver for Redis and move enforceRateLimit's map to the same place.

Backups

The backup task dumps the DB to backups/ every night and uploads it to the private S3 bucket when one is configured. clean drops both copies past NUXT_BACKUP_RETENTION_DAYS.

Encrypting them

A dump is every row you have in one portable file, so the offsite copy is worth encrypting. Set NUXT_BACKUP_AGE_PUBLIC_KEY and backups become .dump.age, encrypted with age.

It is public-key encryption on purpose: the server holds only the public key, so it can write backups but cannot read any of them back, and a compromise of the app or of the bucket yields ciphertext. Never reuse the postgres password for this, it sits in the same env as the S3 credentials, so an attacker who reaches the backups already has it.

age-keygen -o key.txt   # prints the public key, keep key.txt off the server

Put the age1... public key in the server's .env and store key.txt in a password manager plus one offline copy. Lose it and every backup is unrecoverable, so restore one now to check the whole chain works:

pnpm db:restore backups/backup-....dump.age key.txt

Restoring

Backups include the schema, so restore into a database straight from addDB.sh (use the same user and password as the old DB):

./remote-db/postgres/addDB.sh backup

Then restore with pnpm db:restore. It restores into the NUXT_DB of your .env, or of another env file with -e, so keep an env file whose NUXT_DB points at the new DB:

pnpm db:restore -e .env.backup backups/backup-....dump.age key.txt   # encrypted backup
pnpm db:restore -e .env.backup backups/backup-....dump               # unencrypted backup

To roll a database back, restore into a new one and swap the names. Stop the app first, the rename needs zero connections. Restore as the app user, or the tables end up owned by postgres and the app gets permission denied:

psql -c "alter database app rename to old;" \
     -c "alter database backup rename to app;"

Nuxt layer

This repo is published on npm as maxibase. The root of the repo is an example project: copy it to start a new project, then edit what you need.

Using it in a project

pnpm add maxibase
// nuxt.config.ts
export default defineNuxtConfig({
  extends: ['maxibase'],
})

What ships in the package

Path Role
layers/base the layer itself: pages, components, API routes, utils, shared types. Not meant to be edited
nuxt.config.ts default route rules, rate limits, i18n and head config. Your config overrides it
locales default translations. Your locales files are merged on top, key by key
server/assets/emails default email templates. A template in your server/assets/emails replaces the package's one with the same path
scripts deploy, rollback and DB scripts, run from node_modules (see package.json below)
docker default Dockerfile and compose.yaml for pnpm prod, used when your project has none

New translations, templates and config defaults in a new version reach every project with pnpm update maxibase, without copying anything.

To keep a single language, disable the other one in your config:

i18n: { locales: [{ code: 'fr' }, { code: 'en', disabled: true }], defaultLocale: 'fr' }

Starting a project

Copy only these files from this repo, the rest comes from the package:

Path Notes
nuxt.config.ts replace extends: ['./layers/base'] with extends: ['maxibase']
package.json use the one below instead
tsconfig.json, pnpm-workspace.yaml, .gitignore as they are
.env.example fill it in and rename it to .env
drizzle.config.ts used by the db:* scripts
server/database/schema.ts re-exports the layer's tables, add yours below
app/assets/css/index.css required by the css entry of the package config
app/components/TheHeader.vue, TheFooter.vue required by the default layout
app/pages/index.vue replace the example with your home page, the layer links to the index route

And only if you need them:

Path Notes
server/database/relations.ts, access.ts organizations, along with server/utils/organizations.ts and their tables
server/database/seed.ts for db:seed
.oxlintrc.json, .oxfmtrc.json lint and format config

Organizations come as a set: the organizations and organization_members tables in schema.ts, relations.ts, access.ts and server/utils/organizations.ts. Keep all of them or none.

Don't copy locales, server/assets/emails, scripts or docker, they ship with the package. A copied translation or email template replaces the package's one and stops receiving its updates, so copy only the keys or templates you change. layers, test, vitest.config.ts, remote-db and .github are for this repo only (the CI runs scripts a project's package.json doesn't have).

The tables of the auth schema ship with the layer, in layers/base/server/database/schema.ts. Your server/database/schema.ts re-exports them and adds your own tables, so pnpm update maxibase brings their changes: run pnpm db:generate after an update. To customise one, copy it into your schema, a table declared in your file takes precedence over the package's one with the same name. Their relations come from authRelations in layers/base/server/database/relations.ts. To add a relation to one of these tables, define the table in your relations.ts: your entry replaces the layer's for that table, so repeat the layer's relations you still need there.

db.ts, relations.ts and access.ts in server/database are optional, the layer has a default for each in layers/base/server/database/defaults: no relations of your own, and file access to the user's own folder u/{id} or to admins. Add the file to replace the default, then restart the dev server. The migrate script is in the package too, see db:migrate below.

A project's package.json looks like this. Add any package your own files import: pnpm only lets a project import its direct dependencies.

{
  "name": "my-project",
  "private": true,
  "type": "module",
  "scripts": {
    "build": "nuxt build",
    "dev": "nuxt dev",
    "preview": "nuxt preview",
    "postinstall": "nuxt prepare",
    "prod": "bash node_modules/maxibase/scripts/deploy.sh",
    "rollback": "bash node_modules/maxibase/scripts/rollback.sh",
    "db:seed": "npx jiti ./server/database/seed.ts",
    "db:generate": "drizzle-kit generate",
    "db:push": "drizzle-kit push",
    "db:push-prod": "bash node_modules/maxibase/scripts/db/push-server.sh",
    "db:restore": "bash node_modules/maxibase/scripts/db/restore.sh",
    "db:migrate": "npx jiti node_modules/maxibase/scripts/db/migrate.ts",
    "db:studio": "drizzle-kit studio",
    "lint": "oxlint",
    "fmt": "oxfmt",
    "typecheck": "nuxt typecheck"
  },
  "dependencies": {
    "drizzle-orm": "1.0.0-rc.3",
    "goku-css": "^9.0.8",
    "maxibase": "^1.0.0",
    "nuxt": "^4.6.0"
  },
  "devDependencies": {
    "@types/node": "^24.0.0",
    "drizzle-kit": "1.0.0-rc.3",
    "drizzle-seed": "1.0.0-rc.3",
    "oxfmt": "^0.72.0",
    "oxlint": "^1.87.0",
    "typescript": "^5.9.3",
    "vue-tsc": "^3.3.12"
  }
}

Publishing a new version

The first release goes from 0.0.0 with npm version major, which tags it v1.0.0. prepublishOnly runs lint, format check, typecheck and tests before anything is uploaded.

npm version patch   # or minor, or major for a change projects must adapt to
npm publish
git push --follow-tags

Then run pnpm update maxibase in each project. Projects depend on ^1.0.0, so they only move to a new major version when you change that range.

Related Packages

@stackonward/nuxt-layer-base

De-branded Nuxt layer foundation: api-client wiring, session/token store, SEO an...

754
@minch/bentolio

A minimal portfolio template and layer built with Nuxt 4, Tailwind CSS 4, and GS...

3,384 11
@authon/nuxt

Authon Nuxt 3 module — auto-imported composables and middleware

2,931 1

Version History

Version Released License
1.5.0
1.4.0
1.3.0
1.2.1
1.2.0
Pre-releases (1)
Version Released License
0.0.0-stage