How to Create and Configure a Pinia Store with Persistence in Celeris Web
To create and configure a Pinia store with persistence in Celeris Web, initialize a Pinia instance in apps/admin/src/store/index.ts, register the custom persistence plugin from store/plugin/persist.ts with optional AES encryption and key prefixing, then define individual stores using defineStore with a persist option that specifies which fields to save to localStorage or sessionStorage.
Celeris Web uses Pinia as its state management library combined with pinia-plugin-persistedstate for automatic state persistence. This setup allows you to save specific store properties to browser storage with optional encryption, ensuring sensitive data remains secure across page reloads. The following guide walks through the exact implementation found in the Celeris Web repository, covering initialization, plugin configuration, and store definition patterns.
Create the Pinia Instance
In apps/admin/src/store/index.ts, Celeris Web initializes a single Pinia instance that serves as the root state container for the entire Vue application. The file exports a setupStore function that installs Pinia on the Vue app instance and wires up the persistence plugin before the app mounts.
import type { App } from "vue";
import { createPinia } from "pinia";
import { registerPiniaPersistPlugin } from "~/store/plugin/persist";
const store = createPinia(); // 📦 Create Pinia
registerPiniaPersistPlugin(store); // 🔌 Attach persistence plugin
export function setupStore(app: App<Element>) {
app.use(store); // 🚀 Install on Vue app
}
export { store };
Key implementation details:
createPinia()is called once to instantiate the root store.registerPiniaPersistPlugin(store)injects the persisted-state plugin with custom serialization logic.setupStoreis invoked in the main application entry point to attach the store to the Vue app.
Register the Persistence Plugin with Encryption
The persistence logic lives in apps/admin/src/store/plugin/persist.ts. This file configures pinia-plugin-persistedstate with a custom serializer that optionally encrypts data using AES-256-CBC when SHOULD_ENABLE_STORAGE_ENCRYPTION is enabled. It also applies a global key prefix via PERSIST_KEY_PREFIX to avoid collisions with other applications sharing the same origin.
import type { Pinia } from "pinia";
import type { Serializer } from "pinia-plugin-persistedstate";
import { createPersistedState } from "pinia-plugin-persistedstate";
import destr from "destr";
import { createStorageName, EncryptionFactory } from "@celeris/utils";
import type { GlobEnvConfig } from "@celeris/types";
import type { Encryption } from "@celeris/utils";
import {
SHOULD_ENABLE_STORAGE_ENCRYPTION,
STORAGE_CIPHER_IV,
STORAGE_CIPHER_KEY,
} from "~/setting/encryptionSetting";
const persistEncryption: Encryption = EncryptionFactory.createAesEncryption({
key: STORAGE_CIPHER_KEY,
iv: STORAGE_CIPHER_IV,
});
export const PERSIST_KEY_PREFIX = createStorageName(
<GlobEnvConfig>import.meta.env,
);
/* Custom serializer – encrypts when enabled */
function customSerializer(shouldEnableEncryption: boolean): Serializer {
if (shouldEnableEncryption) {
return {
deserialize: (value) => {
const decrypted = persistEncryption.decrypt(value);
return destr(decrypted);
},
serialize: (value) => {
const serialized = JSON.stringify(value);
return persistEncryption.encrypt(serialized);
},
};
}
return {
deserialize: (value) => destr(value),
serialize: (value) => JSON.stringify(value),
};
}
/* Register plugin */
export function registerPiniaPersistPlugin(pinia: Pinia) {
pinia.use(
createPersistedState(
createPersistedStateOptions(PERSIST_KEY_PREFIX),
),
);
}
/* Global options used by the plugin */
export function createPersistedStateOptions(keyPrefix: string) {
return {
storage: localStorage,
key: (id) => `${keyPrefix}__${id}`,
serializer: customSerializer(SHOULD_ENABLE_STORAGE_ENCRYPTION),
};
}
Critical configuration points:
- Key prefixing: All storage keys are prefixed with
${PERSIST_KEY_PREFIX}__to namespace Celeris Web data. - Conditional encryption: The
customSerializerfunction returns an encrypted serializer whenSHOULD_ENABLE_STORAGE_ENCRYPTIONis true, otherwise uses plain JSON. - Global defaults: The plugin attaches to every store unless overridden by local
persistoptions.
Define a Store with Persistence Options
Individual stores declare their persistence behavior using the persist property inside defineStore. In apps/admin/src/store/modules/user.ts, the user store specifies exactly which state fields to survive page reloads using the pick array and dynamically selects between localStorage and sessionStorage based on DEFAULT_PROJECT_SETTING.
import { defineStore } from "pinia";
import { DEFAULT_PROJECT_SETTING } from "~/setting/projectSetting";
import { PermissionCacheTypeConstants } from "@celeris/constants";
import { APP_USER_STORE_ID } from "../constants";
export const useUserStore = defineStore(APP_USER_STORE_ID, {
// 👇 Persistence config specific to this store
persist: {
// Only keep these fields in storage
pick: ["userInfo", "token", "refreshToken", "roleList", "updatedAt"],
// Choose storage based on a global project setting
storage:
DEFAULT_PROJECT_SETTING.permissionCacheType ===
PermissionCacheTypeConstants.LOCAL_STORAGE
? localStorage
: sessionStorage,
},
state: () => ({
// ...state fields (shouldLoggedIn, userInfo, token, etc.)
}),
getters: { /* … */ },
actions: { /* … */ },
});
Store-level persistence strategies:
- Selective persistence: The
pickproperty limits what is saved, reducing storage size and preventing transient or sensitive data from leaking to disk. - Storage selection: The store can switch between
localStorage(persistent across sessions) andsessionStorage(cleared when the tab closes) based on project-wide security settings.
Consume the Store in Components
Once configured, consuming the store follows standard Pinia patterns. Import the store composable and interact with state, getters, and actions. The persistence layer automatically handles serialization, encryption, and storage updates when state changes.
<script setup lang="ts">
import { useUserStore } from "~/store/modules/user";
const userStore = useUserStore();
// Example: login and let persistence handle token storage
await userStore.login({ username, password, remember: true });
</script>
Because the store was defined with a persist block, after login the selected state (e.g., token) is automatically written to the configured storage and restored on page reload without manual intervention.
Summary
- Initialize once: Create the Pinia instance in
apps/admin/src/store/index.tsand register the plugin before mounting the Vue app. - Secure by default: Configure encryption and key prefixing in
apps/admin/src/store/plugin/persist.tsto protect persisted data. - Selective persistence: Use the
persist.pickoption in individual stores to control exactly which state fields survive reloads. - Flexible storage: Toggle between
localStorageandsessionStorageper store based on project settings or specific security requirements.
Frequently Asked Questions
How do I enable encryption for persisted Pinia stores in Celeris Web?
Set SHOULD_ENABLE_STORAGE_ENCRYPTION to true in your encryption settings file. The customSerializer function in apps/admin/src/store/plugin/persist.ts automatically detects this flag and wraps the storage methods with AES-256-CBC encryption using the configured STORAGE_CIPHER_KEY and STORAGE_CIPHER_IV.
Can I choose between localStorage and sessionStorage for different stores?
Yes. In the persist configuration object of any store, set the storage property to either localStorage or sessionStorage. The user store in apps/admin/src/store/modules/user.ts demonstrates this by checking DEFAULT_PROJECT_SETTING.permissionCacheType to determine the appropriate storage mechanism at runtime.
What is the purpose of the key prefix in Celeris Web's persistence setup?
The PERSIST_KEY_PREFIX generated in apps/admin/src/store/plugin/persist.ts namespaces all storage keys with a project-specific string derived from environment variables. This prevents key collisions when multiple applications or instances share the same domain and avoids accidental overwrites of unrelated data.
How do I prevent specific state fields from being persisted?
Use the pick array in your store's persist option to explicitly whitelist only the fields you want saved. Any state property not listed in pick will remain in memory only and will not be written to localStorage or sessionStorage when the state updates. If you need to blacklist specific fields instead, use the omit property (provided by pinia-plugin-persistedstate) in your persist configuration.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →