Client-Side API
NukeBase's client library provides a real-time connection to your database through WebSockets. The client handles connection management, request tracking, and event dispatching automatically.
Connection Setup
The client automatically establishes a secure WebSocket connection:
<script src="NukeBaseSDK.js"></script>
// Connect to the server (returns a Promise)
connectWebSocket().then(() => {
console.log("Connected and ready to use NukeBase");
// Start using NukeBase methods here
});
The connection is automatically maintained:
- Reconnects when browser tabs regain focus
- Uses WSS for HTTPS sites, WS for HTTP sites
- Dispatches events for server notifications
Data Operations
Setting Data
The set() function creates or replaces data at a specific path:
// Set a complete object
set("users.john", { name: "John Doe", age: 32 }).then(response => {
console.log("User created successfully");
});
// Set a single value
set("users.john.email", "john@example.com").then(response => {
console.log(response);
});
Getting Data
Retrieve data with the get() function:
// Get a single user
get("users.john").then(response => {
console.log(response.data); // User data
});
// Get entire collection
get("users").then(response => {
const users = response.data;
// Process users...
});
Updating Data
Update existing data without replacing unspecified fields:
// Update specific fields
update("users.john", {
lastLogin: Date.now(),
loginCount: 42
}).then(response => {
console.log(response);
});
// Update a single property
update("users.john.status", "online").then(response => {
console.log(response);
});
Removing Data
Delete data at a specific path:
// Remove a user
remove("users.john").then(response => {
console.log("User deleted");
});
// Remove a specific field
remove("users.john.temporaryToken").then(response => {
console.log(response);
});
Querying Data
Find data that matches specific conditions:
// Basic equality query
query("users", "child.age == 32").then(response => {
console.log(response.data); // All users age 32
});
// Compound conditions
query("products", "child.price < 100 && child.category == 'electronics'").then(response => {
console.log(response.data); // Affordable electronics
});
// Text search with includes()
query("users", "child.bio.includes('developer')").then(response => {
console.log(response.data); // Users with "developer" in their bio
});
Real-time Subscriptions
Basic Subscriptions
Get real-time updates when data changes:
// Subscribe to changes on a path
const unsubscribe = getSub("value@users.john", event => {
console.log("User data changed:", event.data);
});
// When finished listening
unsubscribe().then(() => {
console.log("Unsubscribed successfully");
});
Query Subscriptions
Subscribe to data matching specific conditions:
// Subscribe to active users
const unsubscribe = querySub("value@users", "child.status == 'online'", event => {
const onlineUsers = event.data;
updateOnlineUsersList(onlineUsers);
});
Changed-Only Subscriptions
Only receive updates when data changes, not on the initial subscription:
// Only get notified of changes
const unsubscribe = getSubChanged("value@activeOrders", event => {
notifyNewOrder(event.data);
});
// With query filtering
const unsubscribe = querySubChanged("value@products",
"child.stock < 5", event => {
alertLowStock(event.data);
});
Operation-Specific Subscriptions
Listen for specific types of operations:
// Listen only for updates to user data
const unsubscribe = getSub("update@users.john", event => {
console.log("User was updated:", event.data);
});
// Listen for new data being set
const unsubscribe = getSub("set@orders", event => {
console.log("New order created:", event.data);
});
// Listen for data removal
const unsubscribe = getSub("remove@users", event => {
console.log("A user was deleted:", event.path);
});
Custom Server Functions
Execute custom logic on the server without exposing implementation details:
// Call the server function
wsFunction("addNumbers", {
num1: 5,
num2: 7
})
.then(response => {
// Display the result returned by the server
console.log(`The sum is: ${response.data}`); // Output: The sum is: 12
});
This straightforward example shows how WebSocket functions allow you to execute code on the server and return results directly to the client, with the return value accessible via the data property of the response.
File Operations
Upload files to your server:
// Upload from a file input
const fileInput = document.getElementById('profilePicture');
const file = fileInput.files[0];
setFile("users.john.profile.jpg", file).then(response => {
showSuccess("Profile picture uploaded!");
updateProfileImage(response.data.url);
});
The file upload process:
- Reads the file as an ArrayBuffer
- Adds path and filename metadata
- Sends the binary data over WebSocket
- Returns the server response (typically with a URL to access the file)
Response Format
All NukeBase operations return a standardized response object:
{
// The operation performed
action: "get",
// Data from the operation
data: {
"user123": { name: "John", age: 32 },
"user456": { name: "Jane", age: 28 }
},
// For tracking the request
requestId: "RH8HZX9P",
// Success or Failed
status: "Success"
}
When an error occurs, the response includes:
{
status: "Failed",
message: "Error description here"
}
Server-Side API
NukeBase is designed for simplicity. To get up and running, you only need:
- server/database.exe: The core database engine
- server/data.json: Your database file
- server/rules.js: JSON security rules (coming soon)
- server/app.js: Your configuration file
- public/(index.html, ...): For serving HTML files
That's it! No complex configuration or additional services required.
Setup and Initialization
NukeBase server runs as a Node.js application with a simple setup process. The key components are:
- database.exe: Core database engine that must be in your project's root directory
- app.js: Configuration file that sets up domains, middleware, and event handlers
Basic Server Structure
The server configuration is defined using a module export function that receives core dependencies:
module.exports = (fs, express, addFunction, functionMatch, addWsFunction, get, set, update, remove, query, generateRequestId, data, addDomain, startDB, onConnection, onClose) => {
// Server configuration goes here
}
Domain Configuration
NukeBase supports multiple domains with custom SSL certificates using a single server instance:
const productionDomain = addDomain({
domain: "example.com",
https: {
key: '/etc/letsencrypt/live/example.com/privkey.pem',
cert: '/etc/letsencrypt/live/example.com/fullchain.pem'
}
});
// You can add as many domains as needed
const anotherDomain = addDomain({
domain: "another-domain.com",
https: {
key: '/etc/letsencrypt/live/another-domain.com/privkey.pem',
cert: '/etc/letsencrypt/live/another-domain.com/fullchain.pem'
}
});
For development environments, you can use empty strings for the SSL credentials:
const devDomain = addDomain({
domain: "exampledomain.com",
https: {
key: '',
cert: ''
}
});
Express Middleware
Each domain has its own Express app instance that you can configure:
// Serve static files
myDomain.app.use(express.static(path.join(process.cwd(), `../public`)));
// Serve files with long cache time
myDomain.app.use('/files', express.static(path.join(process.cwd(), `../files`), {
maxAge: 60 * 60 * 24 * 1000 * 365 // 1 year in milliseconds
}));
Database Triggers
Create event-driven functions that respond to database changes:
// Create a trigger for when a request is updated
addFunction("onUpdate", "requests.$requestId", async function(context) {
// The context object contains all relevant information about the change
const beforeNotes = context.dataBefore?.notes;
const afterNotes = context.dataAfter?.notes;
// Replace "pizza" with pizza emoji
const newNotes = afterNotes.replaceAll("pizza", "🍕");
// Avoid infinite loop by checking if we already replaced
if (newNotes === afterNotes) {
return;
}
// Update the data with our modified version
update(context.path, { notes: newNotes });
});
Key components of database triggers:
addFunction(eventType, pathPattern, callbackFunction)
Event Types
"onSet"
- Triggered when data is created or completely replaced"onUpdate"
- Triggered when data is partially updated"onRemove"
- Triggered when data is deleted"onValue"
- Triggered for all changes (set, update, remove)
Path Patterns
Use a path string with wildcards to match specific data paths:
"users.$userId"
- Matches any user path like "users.john" or "users.alice""posts.$postId.comments.$commentId"
- Matches any comment on any post
Context Object
Your callback function receives a context object containing:
context.path
- The complete path that was changedcontext.dataAfter
- The data after the change (null for remove operations)context.dataBefore
- The data before the change (null for new data)
Important: When modifying data within a trigger that affects the same path you're watching, always implement safeguards to prevent infinite loops, as shown in the example.
Complete Example: Order Processing
// React to new orders being created
addFunction("onSet", "orders.$orderId", async function(context) {
// Only run if this is a new order (no previous data)
if (!context.dataBefore && context.dataAfter) {
// Extract orderId from the path
const orderId = context.path.split('.')[1];
// Update the order status
await update(context.path, {
status: "processing",
processingStart: Date.now()
});
}
});
WebSocket Functions
Create custom server functions that clients can call through wsFunction:
addWsFunction("addNumbers", function (data, admin, sessionId, userId) {
// Extract numbers from the request
const { num1, num2 } = data;
// Perform the calculation on the server
const sum = num1 + num2;
// Return the result to the client
return sum;
});
WebSocket functions receive:
- Client-sent data
- Admin flag (for protected operations)
- User's session ID
- User's ID
Connection Events
Track client connections and disconnections:
// When a client connects
onConnection(function (userId, sessionId) {
// Record session start time
update(`sessions.${userId}.${sessionId}`, {
start: Date.now()
});
});
// When a client disconnects
onClose(function (userId, sessionId) {
// Record session end time
update(`sessions.${userId}.${sessionId}`, {
end: Date.now()
});
});
Starting the Database
Start the NukeBase server with configuration options by calling startDB() once at the end of your configuration:
// Local IP Address mode (no SSL)
startDB({ local: true, http: "127.0.0.1", port: 3000 });
// Server IP Address mode (no SSL)
startDB({ local: true, http: "126.23.45.1", port: 3000 });
// Production mode (with SSL)
startDB({ local: false, http: "0.0.0.0", port: 3000 });
Configuration options:
- local: Boolean - use IP address provided
- When true: Use HTTP without SSL, binding to the specified IP address
- When false: Use HTTPS with the configured SSL certificates
- http: String - the IP address to bind to
- Use "127.0.0.1" to accept connections only from the local machine
- Use a specific IP address like "126.23.45.1" to bind to that server address
- Use "0.0.0.0" to accept connections from any IP
- port: Number - the starting port address (Increments on multiple domains)
- For a single domain: The server will listen on this port
- For multiple domains: Each domain gets its own port, starting with this number and incrementing (e.g., 3000, 3001, 3002...)
Complete Server Example
Here's a minimal but complete server setup:
module.exports = (fs, express, addFunction, addWsFunction, get, set, update, remove, query, generateRequestId, data, addDomain, startDB, onConnection, onClose) => {
// Set up a domain
const myApp = addDomain({
domain: "myapp.com",
https: {
key: '/etc/letsencrypt/live/myapp.com/privkey.pem',
cert: '/etc/letsencrypt/live/myapp.com/fullchain.pem'
}
});
// Configure middleware for serving static files
const path = require('path');
myApp.app.use(express.static(path.join(process.cwd(), 'public')));
// Add a database trigger for important changes
addFunction("onValue", "orders.$orderId", async function(context) {
// Only trigger if data has actually changed
if (JSON.stringify(context.dataAfter) !== JSON.stringify(context.dataBefore)) {
await set(logs.${generateRequestId()}, {
path: context.path,
timestamp: Date.now(),
oldValue: context.dataBefore,
newValue: context.dataAfter,
change: "Important data changed"
});
}
});
// Add a WebSocket function for client calculations
addWsFunction("addNumbers", function(data, admin, sessionId, userId) {
// Extract numbers from the request
const { num1, num2 } = data;
// Perform the calculation on the server
const sum = num1 + num2;
// Return the result to the client
return sum;
});
// Track user connections
onConnection(function(userId, sessionId) {
// Record when user connects
update(sessions.${userId}.${sessionId}, {
start: Date.now(),
userAgent: data.request?.headers?.["user-agent"] || "Unknown"
});
// Update user status
update(`users.${userId}`, {
online: true,
lastSeen: Date.now()
});
});
// Handle user disconnections
onClose(function(userId, sessionId) {
// Record when user disconnects
update(sessions.${userId}.${sessionId}, {
end: Date.now(),
duration: function(current) {
return current.end - current.start;
}
});
// Update user status
update(`users.${userId}`, {
online: false,
lastSeen: Date.now()
});
});
// Start the server (local mode on port 3000)
startDB({ local: true, http: "127.0.0.1", port: 3000 });
console.log(🚀 NukeBase server running on Node.js "http://127.0.0.1:3000");
};
Note: This example demonstrates best practices including:
- Domain setup with SSL configuration
- Static file serving
- Real-time database triggers
- Custom WebSocket functions
- Connection tracking
- Server initialization with proper port configuration
Complete Client Example
Here's a minimal but complete client setup:
const urlParam = new URLSearchParams(location.search);
function generateRequestId() {
const chars = '0123456789ABCDEFGHJKLMNPQRSTUVWXYZ';
let result = '';
for (let i = 0; i < 8; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
}
function sendRequest(action, path, socket, data) {
return new Promise((resolve, reject) => {
admin = urlParam.get("admin") || "";
const requestId = generateRequestId();
pendingRequests[requestId] = { resolve, reject };
socket.send(JSON.stringify({ action, path, requestId, data, admin }));
});
}
function sendSubscribe(action, path, socket, data) {
admin = urlParam.get("admin") || "";
socket.send(JSON.stringify({ action, path, data, admin }));
}
const sub = {
events: {},
on(action, path, callback, data) {
this.events[action + path] = callback;
sendSubscribe(action, path, socket, data);
},
emit(action, path, data) {
if (this.events[action + path]) {
this.events[action + path](data);
}
},
off(action, path, callback, data) {
sendSubscribe(action + 'Stop', path, socket, data)
delete this.events[action + path];
}
};
var pendingRequests = {};
var socket;
function connectWebSocket() {
return new Promise((resolve, reject) => {
const wsProtocol = window.location.protocol === "https:" ? "wss://" : "ws://";
socket = new WebSocket(`${wsProtocol}${window.location.host}`);
socket.addEventListener('message', function (event) {
try {
const response = JSON.parse(event.data);
if (response.requestId) {
pendingRequests[response.requestId].resolve(response);
delete pendingRequests[response.requestId];
} else {
sub.emit(response.action, response.path, response);
}
} catch (error) {
console.error('Error parsing message:', error);
}
});
socket.addEventListener('open', async () => {
console.log('WebSocket connection opened');
resolve();
});
socket.addEventListener('close', () => {
console.log('WebSocket connection closed');
});
socket.addEventListener('error', (error) => {
console.error('WebSocket error:', error);
});
});
}
//Reconnect WebSocket when the browser window gains focus
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
setTimeout(function () {
if (socket.readyState === WebSocket.CLOSED) {
connectWebSocket();
}
}, 500);
}
});
function set(path, data) {
return sendRequest("set", path, socket, data).then(data => {
return data;
})
}
function get(path) {
return sendRequest('get', path, socket).then(data => {
return data;
})
}
function update(path, data) {
return sendRequest("update", path, socket, data).then(data => {
return data;
})
}
function remove(path) {
return sendRequest("remove", path, socket).then(data => {
return data;
})
}
function query(path, query) {
return sendRequest('query', path, socket, query).then(data => {
return data;
})
}
function wsFunction(name, data) {
return sendRequest(name, null, socket, data).then(data => {
return data;
})
}
function getSub(path, handler) {
sub.on("getSub", path, handler);
return function () {
sub.off('getSub', path);
};
}
function querySub(path, query, handler) {
sub.on("querySub", path, handler, query);
return function () {
sub.off("querySub", path, handler, query);
}
}
function getSubChanged(path, handler) {
sub.on("getSubChanged", path, handler);
return function () {
sub.off('getSubChanged', path);
};
}
function querySubChanged(path, query, handler) {
sub.on("querySubChanged", path, handler, query);
return function () {
sub.off("querySubChanged", path, handler, query);
}
}
function setFile(path, file) {
return new Promise((resolve, reject) => {
const requestId = generateRequestId();
pendingRequests[requestId] = { resolve, reject };
const separator = "--myUniqueSeparator--";
const reader = new FileReader();
reader.onload = function (e) {
const arrayBuffer = e.target.result;
const textEncoder = new TextEncoder();
const encodedPath = textEncoder.encode(path + separator);
const encodedFileName = textEncoder.encode(file.name + separator);
const encodedRequestId = textEncoder.encode(requestId + separator);
const combinedArrayBuffer = new Uint8Array(
encodedPath.length + encodedFileName.length + encodedRequestId.length + arrayBuffer.byteLength
);
combinedArrayBuffer.set(encodedPath, 0);
combinedArrayBuffer.set(encodedFileName, encodedPath.length);
combinedArrayBuffer.set(encodedRequestId, encodedPath.length + encodedFileName.length);
combinedArrayBuffer.set(
new Uint8Array(arrayBuffer), encodedPath.length + encodedFileName.length + encodedRequestId.length
);
socket.send(combinedArrayBuffer.buffer);
};
reader.readAsArrayBuffer(file);
});
}
connectWebSocket().then(() => {
// Example usage
set("users.matt.color", "red").then(data => {
console.log(data);
})
get("sessions").then(data => {
console.log(data);
})
update("users.matt", { leadsSent: "Pending" }).then(data => {
console.log(data);
})
update("users.matt.count", 5).then(data => {
console.log(data);
})
remove("users.matt").then(data => {
console.log(data);
})
query("sessions", `child.count > 0`).then(data => {
console.log(data);
})
wsFunction("custom1", 23).then(data => {
console.log(data);
})
//all subscriptions use value, get, update, remove. This subscribes to that action
getSub("value@sessions", data => {
console.log(data);
});
querySub("value@sessions", "child.count == 4", data => {
console.log(data);
});
getSubChanged("value@sessions", data => {
console.log(data);
});
querySubChanged("value@sessions", "child.count != 4", data => {
console.log(data);
});
setFile(undefined, blob).then(data => {
console.log(data);
})
});