Try Live
Add Docs
Rankings
Pricing
Enterprise
Docs
Install
Theme
Install
Docs
Pricing
Enterprise
More...
More...
Try Live
Rankings
Create API Key
Add Docs
racletteJS
https://gitlab.com/raclettejs/docs
Admin
racletteJS is a web-based, fullstack development framework designed for building modular
...
Tokens:
50,565
Snippets:
616
Trust Score:
7.5
Update:
2 weeks ago
Context
Skills
Chat
Benchmark
62.1
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# racletteJS Documentation racletteJS is a fullstack framework designed for building platforms and portals. It provides essential building blocks for business applications including authentication, multi-tenancy, auto-generated APIs, and UI components, allowing developers to focus on unique business logic rather than infrastructure. The framework uses a modular plugin architecture with Node.js/Fastify backend, MongoDB database, and Vue.js or React frontend support. The framework follows the principle that common patterns should be effortless while custom logic remains powerful. It handles user authentication with RBAC, data organization via APIs, dashboards and workflows, and multi-tenant scaling out of the box. The built-in Workbench provides a visual admin interface for managing compositions, users, and application configuration without code. ## Installation and Project Setup Initialize a new racletteJS project using the CLI tool or manual installation. The CLI wizard guides you through project creation and sets up all necessary files and folders. ```bash # Using npx (recommended) npx create-raclette-app # Using yarn yarn create raclette-app # Manual installation of core packages yarn add @raclettejs/core @raclettejs/workbench ``` ## Configuration File (raclette.config.js) The main configuration file defines your application name, services, frontend framework, and backend settings. Use `defineRacletteConfig` for type-safe configuration. ```javascript // raclette.config.js import { defineRacletteConfig } from "@raclettejs/core" export default defineRacletteConfig({ name: "my-raclette-app", services: { client: { enabled: true, port: 8081, nodeModulesVolume: "client_node_modules", }, backend: { enabled: true, port: 3000, enableDebug: true, }, mongodb: { enabled: true, port: 27017, databaseName: "myapp", }, redis: { enabled: true, port: 6379, }, workbench: { enabled: true, port: 8083, }, }, frontend: { framework: "vue", vue: { plugins: ["vue-router"], }, }, backend: { sockets: { autoSend: { compositions: true, interactionLinks: true, projectConfig: true, additionalDatatypes: {}, }, security: { requireAuth: true, tokenValidation: "jwt", }, options: { adapter: "redis", connectionTimeout: 30000, pingInterval: 25000, pingTimeout: 5000, }, }, }, env: { development: { API_URL: "http://localhost:3000", }, production: { API_URL: "https://api.myapp.com", }, }, }) ``` ## CLI Commands The raclette CLI provides commands for development, container management, and project scaffolding. Add these scripts to your package.json for easy access. ```bash # Start development environment (Docker Compose) yarn dev # Run services directly without containers (faster iteration) yarn dev --direct # Filter logs to specific services yarn dev --filter frontend,backend # Force rebuild Docker images yarn dev --force-rebuild # Stop all services yarn down # Stop project services but keep shared services (MongoDB, Redis) yarn down --keep-shared # Restart specific services yarn restart frontend backend # Update dependencies in containers yarn update backend yarn update frontend yarn update both # Add packages to a target yarn add-package frontend lodash yarn add-package backend express --dev yarn add-package both typescript --dev # Rebuild Docker images yarn rebuild frontend # Build for production yarn build ``` ## Plugin Metadata (raclette.plugin.ts) Define plugin metadata, dependencies, and lifecycle hooks in the raclette.plugin.ts file. This file is required for every plugin. ```typescript // plugins/my-plugin/raclette.plugin.ts export default { name: "my-plugin", author: "Your Name", version: "1.0.0", description: "A custom plugin for my application", repository: "https://github.com/user/my-plugin", license: "MIT", // Plugin dependencies that must be installed first dependencies: ["core-ui", "data-manager"], // Plugin-specific configuration options options: { maxConnections: 100, enableCache: true, theme: "dark", }, // Lifecycle hooks hooks: { beforeMount: async (app) => { console.log("Plugin is about to be mounted") // Perform pre-mount setup }, mounted: async (app) => { console.log("Plugin has been mounted") // Register global components, start services app.component("MyGlobalComponent", MyComponent) }, beforeUnmount: async (app) => { console.log("Plugin is about to be unmounted") // Cleanup tasks: stop services, clear cache }, }, } ``` ## Backend Plugin Entry Point (index.ts) The main backend plugin file orchestrates models, services, routes, and schemas. It registers all components with the Fastify instance. ```typescript // plugins/todo/backend/index.ts import type { PluginOptions, PluginFastifyInstance } from "@raclettejs/core" import { createModels } from "./todo.model" import { registerRoutes } from "./routes" import { registerPayload } from "./helpers/todoHelper" import { registerTodoSchemas } from "./todo.schema" import { registerTodoCrud } from "./helpers/crud" import { createTodoService } from "./todo.service" const todoPlugin = async ( fastify: PluginFastifyInstance, opts: PluginOptions ) => { // Create and register database models const models = createModels(fastify) // Initialize service layer and inject into Fastify const todoService = createTodoService(models.todo) fastify.todoService = todoService // Register routes with error handling try { await fastify.register((instance) => registerRoutes(instance, models, opts)) } catch (error) { fastify.log.error(`Failed to register routes.`, error) throw error } // Register additional components registerPayload(fastify) registerTodoSchemas(fastify) registerTodoCrud(fastify) } export default todoPlugin ``` ## Database Models (Mongoose) Define database schemas using Mongoose with UUID-based primary keys, soft deletion support, and automatic timestamps. ```typescript // plugins/todo/backend/todo.model.ts import type { PluginFastifyInstance } from "@raclettejs/core" import type { Document } from "mongoose" import { Schema } from "mongoose" import { v4 as uuidv4 } from "uuid" export const MODEL_BASENAME = "Todo" export interface ITodo extends Document<string, unknown, Todo> { name: string content: string isDeleted: boolean tags: Array<string> owner: string lastEditor: string } const TodoSchema: Schema = new Schema( { _id: { type: String, default: () => uuidv4(), required: true, }, name: { type: String, required: true }, isDeleted: { type: Boolean, required: false, default: false }, content: { type: String, required: false }, tags: { type: Array, required: false }, owner: { type: Schema.Types.String, ref: "raclette__core-user", required: true, }, lastEditor: { type: Schema.Types.String, ref: "raclette__core-user", required: true, }, }, { timestamps: true } ) export const createModels = (fastify: PluginFastifyInstance) => { const todoModel = fastify.createModel(MODEL_BASENAME, TodoSchema) return { todo: todoModel } } ``` ## TypeBox Schema Definitions Define validation schemas using TypeBox for runtime validation and TypeScript type generation. ```typescript // plugins/todo/backend/todo.schema.ts import type { Static } from "@sinclair/typebox" import { Type } from "@sinclair/typebox" const baseTodoSchema = { name: Type.String(), content: Type.Optional(Type.String()), tags: Type.Optional(Type.Array(Type.String(), { default: [] })), owner: Type.String(), lastEditor: Type.Optional(Type.String()), isDeleted: Type.Optional(Type.Boolean({ default: false })), } // Full schema for responses (includes timestamps) export const todoSchema = Type.Object( { _id: Type.String(), ...baseTodoSchema, createdAt: Type.String({ format: "date-time" }), updatedAt: Type.String({ format: "date-time" }), }, { $id: "#todo/base", title: "core/todo" } ) // Create schema for POST operations export const todoCreateSchema = Type.Object( { _id: Type.Optional(Type.String()), ...baseTodoSchema, }, { $id: "#todo/create", title: "core/todo-create" } ) // Update schema for PATCH operations export const todoUpdateSchema = Type.Object( { name: Type.Optional(Type.String()), content: Type.Optional(Type.String()), tags: Type.Optional(Type.Array(Type.String())), lastEditor: Type.Optional(Type.String()), isDeleted: Type.Optional(Type.Boolean()), }, { $id: "#todo/update", title: "core/todo-update" } ) // TypeScript type exports export type Todo = Static<typeof todoSchema> export type TodoCreate = Static<typeof todoCreateSchema> export type TodoUpdate = Static<typeof todoUpdateSchema> ``` ## Service Layer Encapsulate business logic in service classes with dual method patterns: core methods return raw data, public methods return framework payloads. ```typescript // plugins/todo/backend/todo.service.ts import { v4 as uuidv4, validate } from "uuid" import type { Model } from "mongoose" import type { PluginFastifyInstance, FrontendPayload, FrontendPayloadRequestData } from "@raclettejs/core" import type { Todo as TodoType, TodoCreate } from "./todo.schema" import { createTodoPayload } from "./helpers/todoHelper" export class TodoService { private todoModel: Model<TodoType> constructor(model: Model<TodoType>) { this.todoModel = model } // Core method - returns raw data async _createTodo( fastify: PluginFastifyInstance, todoBody: TodoCreate ): Promise<TodoType> { // UUID validation and duplicate checking if (todoBody._id) { const uuidValid = validate(todoBody._id) if (!uuidValid) { throw new Error("Invalid ID - not a valid uuid v4") } const duplicate = await this.todoModel.findById(todoBody._id) if (duplicate) { throw new Error("An entry with this id already exists") } } else { todoBody._id = uuidv4() } const todo = new this.todoModel(todoBody) await todo.save() fastify.log.info(`[API] Created todo #${todo._id}`) return todo.toObject ? todo.toObject() : todo } // Public method - returns wrapped payload with event emission async createTodo( fastify: PluginFastifyInstance, requestData: FrontendPayloadRequestData, todoBody: TodoCreate ): Promise<FrontendPayload<TodoType[]>> { const todo = await this._createTodo(fastify, todoBody) const payload = await createTodoPayload(fastify, [todo], requestData) if (requestData.broadcast) { fastify.emit("coreTodoCreated", payload) } return payload } // Read with soft deletion filter async _readTodos( filter: Record<string, any> = { isDeleted: false }, options: { limit?: number; offset?: number } = {} ): Promise<TodoType[]> { filter = { isDeleted: false, ...filter } let query = this.todoModel.find(filter) if (options.limit !== undefined) query = query.limit(options.limit) if (options.offset !== undefined) query = query.skip(options.offset) return await query.lean() } // Soft delete async _removeTodo(id: string): Promise<TodoType | null> { return await this.todoModel .findByIdAndUpdate(id, { isDeleted: true }, { new: true }) .lean() } } export const createTodoService = (model: Model<TodoType>) => new TodoService(model) ``` ## Route Registration Register API routes with Fastify using a centralized route registration pattern. ```typescript // plugins/todo/backend/routes/index.ts import type { PluginFastifyInstance, PluginOptions } from "@raclettejs/core" import type { Model } from "mongoose" import getAllRoute from "./route.todo.get-all" import getByIdRoute from "./route.todo.get" import postRoute from "./route.todo.post" import patchRoute from "./route.todo.patch" import deleteRoute from "./route.todo.delete" export const registerRoutes = async ( fastify: PluginFastifyInstance, models: Record<string, Model<any>>, options: PluginOptions ) => { const { key: pluginKey } = options fastify.get("/all", getAllRoute(fastify)) fastify.get("/todo/:_id", getByIdRoute(fastify)) fastify.post("/todo", postRoute(fastify)) fastify.patch("/todo/:_id", patchRoute(fastify)) fastify.delete("/todo/:_id", deleteRoute(fastify)) } ``` ## Route Handler Pattern Create individual route handlers as factory functions that return route configuration with authentication, validation, and documentation. ```typescript // plugins/todo/backend/routes/route.todo.post.ts import type { TodoCreate } from "../todo.schema" import type { FastifyReply, FastifyRequest } from "fastify" import { todoCreateSchema } from "../todo.schema" import type { PluginFastifyInstance } from "@raclettejs/core" export default (fastify: PluginFastifyInstance) => { const handler = async ( req: FastifyRequest<{ Body: TodoCreate }>, reply: FastifyReply ) => { try { const todoData = { ...req.body, owner: req.user._id, lastEditor: req.user._id, } const payload = await fastify.todoService.createTodo( fastify, req.requestParams, todoData ) return reply.status(201).send(payload) } catch (err: any) { fastify.log.error(`Error creating todo: ${err.message}`) return reply.internalServerError(err.message) } } return { handler, onRequest: [fastify.authenticate], config: { type: "dataCreate" }, schema: { summary: "Create a new todo", description: "Create a new todo item", tags: ["core/todo"], body: todoCreateSchema, }, } } ``` ## Frontend Plugin Definition Define frontend plugin configuration with install hooks, internationalization, data definitions, and exported components. ```typescript // plugins/weather/frontend/index.ts import { defineRaclettePluginFrontend } from "@raclettejs/core/frontend" import WeatherWidget from "./components/WeatherWidget.vue" import WeatherCard from "./components/WeatherCard.vue" export default defineRaclettePluginFrontend({ install: async ($installApi, $corePluginApi) => { $corePluginApi.$log.info("Initializing weather plugin") // Add custom data type $installApi.addDataType("weather", { schema: { temperature: "number", humidity: "number", location: "string", }, }) // Setup error handling via eventbus $corePluginApi.$eventbus.on("weather:error", (error) => { $corePluginApi.$log.error("Weather error:", error) }) }, i18n: { en: { "weather.current": "Current Weather", "weather.forecast": "Forecast", "weather.temperature": "Temperature", "weather.loading": "Loading weather data...", }, fr: { "weather.current": "Meteo Actuelle", "weather.forecast": "Previsions", "weather.temperature": "Temperature", "weather.loading": "Chargement des donnees meteo...", }, }, data: { weatherData: { type: "weather", operations: { get: { target: (payload) => `/api/weather/${payload.location}`, method: "GET", }, getAll: { target: "/api/weather/locations", method: "GET", }, create: { target: "/api/weather/locations", method: "POST", }, update: { target: (payload) => `/api/weather/locations/${payload.locationId}`, method: "PUT", broadcast: true, channels: [{ channel: "weather-updates", channelKey: "locationId", prefix: "weather", }], }, delete: { target: (payload) => `/api/weather/locations/${payload.locationId}`, method: "DELETE", }, }, offlineMode: true, }, }, exportComponents: { WeatherCard: WeatherCard, WeatherIcon: () => import("./components/WeatherIcon.vue"), }, }) ``` ## usePluginApi Composable Access all plugin APIs through the usePluginApi composable. It provides $data, $store, $socket, $eventbus, and $log. ```typescript // In a Vue component setup() import { usePluginApi } from "@raclettejs/core/orchestrator/composables" const { $data, $store, $socket, $eventbus, $log } = usePluginApi() // Access another plugin's API const { $data: $globalData } = usePluginApi("raclette__core") // With custom action ID for query identification const { $data: $todoData } = usePluginApi({ pluginKey: "my-plugin", actionId: "todoList", }) // Fully controlled query ID $todoData.todo.getAll({ params: { isDeleted: false }, id: "main", // Results in: plugin-my-plugin-todoList-main }) ``` ## $data API - Backend Communication The $data API is the primary interface for CRUD operations with reactive state, caching, and real-time sync integration. ```typescript const { $data } = usePluginApi() // Fetch data with reactive state const { data: todos, // Reactive ref with data isLoading, // Loading state error, // Error state execute, // Manual trigger function response, // Raw response } = $data.todo.getAll({ params: { isDeleted: false }, options: { immediate: true }, // Auto-fetch on mount }) // Create with callback to refetch const { execute: createTodo } = $data.todo.create({ options: { cb: () => execute(), // Refetch after creation }, }) // Execute create operation await createTodo({ name: "New Todo", content: "Todo content", }) // Get single item by ID const { data: singleTodo } = $data.todo.get({ params: { _id: "uuid-123" }, options: { immediate: true }, }) // Update item const { execute: updateTodo } = $data.todo.update() await updateTodo({ _id: "uuid-123", name: "Updated Name", }) // Delete item const { execute: deleteTodo } = $data.todo.delete() await deleteTodo({ _id: "uuid-123" }) ``` ## $store API - Reactive State Management Access and mutate application state with Redux-based reactivity wrapped in Vue refs. ```typescript const { $store } = usePluginApi() // Generic state access (returns reactive ref) const uiState = $store.get(["ui"]) const widgetState = $store.get(["widgets", widgetId]) const queryState = $store.get(["queries", actionId]) // Specialized getters const ui = $store.getUiState() const widget = $store.getWidgetState() // Current widget const query = $store.getQueryState("plugin-myPlugin-todo-getAll-abc") // Mutations - all trigger reactive updates $store.setUiState({ navigationOpen: true }) $store.setWidgetState({ selectedItem: "item-1" }) $store.updateWidgetState({ count: 1 }) // Merge update $store.updateConfig({ theme: "dark" }) $store.updatePublic({ sharedData: "value" }) $store.updateDataItem("uuid-123", { name: "Updated" }) // State comparison utility const hasUnsavedChanges = $store.hasChanges(originalState, currentState) ``` ## $socket API - Real-time Events Listen to backend events for real-time UI updates and data synchronization. ```typescript const { $data, $socket, $eventbus } = usePluginApi() const { execute } = $data.todo.getAll({ options: { immediate: true }, }) // Listen for backend events $socket.on("todoCreated", (payload) => { console.log("Todo created:", payload) execute() // Refetch data }) $socket.on("todoUpdated", (payload) => { execute() }) $socket.on("todoDeleted", (payload) => { // Show notification and refetch $eventbus.global.emit("ui/addToSnackbar", { message: "Todo was deleted", color: "yellow", }) execute() }) ``` ## $eventbus API - Event Communication Enable decoupled communication between components and plugins using an EventEmitter pattern. ```typescript const { $eventbus } = usePluginApi() // Plugin-local events (scoped to your plugin) $eventbus.on("myEvent", (payload) => { console.log("Received:", payload) }) $eventbus.emit("myEvent", { foo: "bar" }) // Global events (shared across all plugins) $eventbus.global.on("someGlobalEvent", (payload) => { // payload has { sender: "<pluginKey>", data: {...} } console.log(`From ${payload.sender}:`, payload.data) }) $eventbus.global.emit("ui/addToSnackbar", { message: "Operation successful!", color: "green", }) // One-time listener (auto-removes after trigger) $eventbus.once("initComplete", () => { console.log("Initialization finished") }) // Remove listener const removeListener = $eventbus.on("event", callback) removeListener() // Cleanup ``` ## $log API - Structured Logging Use structured logging with automatic plugin context and timestamps. ```typescript const { $log } = usePluginApi() // Basic logging: $log(title, message, level) $log("init", "Component mounted", 1) // Output: plugin/<plugin-name> - init // { message: "Component mounted", dateTime: "21.04.2026 - 17:30:40:615" } // Log objects $log("data", { items: 5, status: "loaded" }, 1) // Log levels convention: // 0 = error // 1 = info // 2 = debug $log("error", "Failed to fetch data", 0) $log("debug", { query: params, result: data }, 2) ``` ## Widget Declaration Define widget metadata and configurable properties for the drag-and-drop editor. ```typescript // plugins/myPlugin/frontend/widgets/ChartWidget/setup.ts import type { WidgetDeclaration } from "@raclettejs/core" import type { PropType } from "vue" export const details = { title: "Chart Widget", color: "#51E064", icon: new URL("./icon.svg", import.meta.url).href, images: [new URL("./screenshot.png", import.meta.url).href], description: "Display data in various chart formats", } satisfies WidgetDeclaration export const config = { currentChart: { type: String as PropType<"bar" | "line" | "pie">, default: "bar", editor: { inputType: "select", selectionValues: ["bar", "line", "pie"], }, }, showLegend: { type: Boolean, default: true, editor: { inputType: "checkbox", }, }, maxItems: { type: Number, default: 10, editor: { inputType: "number", min: 1, max: 100, }, }, } export default { config, details } ``` ## Widget Vue Component Create widget components using the plugin API composables for data access and state management. ```vue <!-- plugins/myPlugin/frontend/widgets/TodoList/TodoListWidget.vue --> <template> <div class="todo-widget"> <h2>{{ $i18n.t('todo.title') }}</h2> <div v-if="isLoading">{{ $i18n.t('todo.loading') }}</div> <div v-else-if="error">{{ $i18n.t('todo.error') }}</div> <ul v-else> <li v-for="todo in todos" :key="todo._id"> {{ todo.name }} <button @click="deleteTodo(todo._id)">Delete</button> </li> </ul> <form @submit.prevent="handleCreate"> <input v-model="newTodoName" placeholder="New todo..." /> <button type="submit">Add</button> </form> </div> </template> <script setup lang="ts"> import { ref } from "vue" import { usePluginApi } from "@raclettejs/core/orchestrator/composables" const { $data, $socket, $eventbus, $i18n, $log } = usePluginApi() const newTodoName = ref("") // Fetch todos with reactive state const { data: todos, isLoading, error, execute } = $data.todo.getAll({ params: { isDeleted: false }, options: { immediate: true }, }) // Create mutation with refetch callback const { execute: createTodo } = $data.todo.create({ options: { cb: () => execute() }, }) // Delete mutation const { execute: removeTodo } = $data.todo.delete({ options: { cb: () => execute() }, }) // Real-time sync $socket.on("todoCreated", () => execute()) $socket.on("todoDeleted", () => execute()) const handleCreate = async () => { if (!newTodoName.value.trim()) return await createTodo({ name: newTodoName.value }) newTodoName.value = "" $eventbus.global.emit("ui/addToSnackbar", { message: "Todo created!", color: "green", }) } const deleteTodo = async (id: string) => { await removeTodo({ _id: id }) $log("action", `Deleted todo ${id}`, 1) } </script> ``` racletteJS is ideal for building SaaS applications, customer portals, internal tools, and multi-tenant platforms. The plugin architecture enables modular development where each feature is self-contained with its own models, services, routes, and UI components. The framework handles authentication, real-time updates, and state management, allowing teams to focus on business-specific functionality. Integration patterns center around the plugin system: backend plugins define Fastify routes and Mongoose models, while frontend plugins define Vue/React components and data operations. The Workbench provides no-code configuration for compositions (page layouts), user management, and application settings. For real-time features, combine $data for mutations with $socket for event-driven updates, creating a request-event-sync loop that keeps all clients synchronized.