diff --git a/API_USAGE.md b/API_USAGE.md deleted file mode 100644 index 5d3d30ac9..000000000 --- a/API_USAGE.md +++ /dev/null @@ -1,145 +0,0 @@ -# urBackend API Usage Guide πŸ“– - -This guide provides technical examples for interacting with the urBackend API. Once your project is created in the dashboard, use your **Public API Key** (for frontend) or **Secret API Key** (for backend) to make requests. - -## Base URL - -``` -https://api.urbackend.bitbros.in -``` - -## 1. Authentication - -Manage users for your own applications using urBackend's built-in auth system. - -### Sign Up User -```javascript -await fetch('https://api.urbackend.bitbros.in/api/userAuth/signup', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'x-api-key': 'YOUR_API_KEY' - }, - body: JSON.stringify({ - email: "user@example.com", - password: "securePassword123", - name: "John Doe" // Optional - }) -}); -``` - -### Login User -```javascript -const res = await fetch('https://api.urbackend.bitbros.in/api/userAuth/login', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'x-api-key': 'YOUR_API_KEY' - }, - body: JSON.stringify({ - email: "user@example.com", - password: "securePassword123" - }) -}); -const data = await res.json(); // Returns { token: "JWT_TOKEN", user: {...} } -``` - -### Get Profile (Me) -```javascript -await fetch('https://api.urbackend.bitbros.in/api/userAuth/me', { - method: 'GET', - headers: { - 'x-api-key': 'YOUR_API_KEY', - 'Authorization': 'Bearer ' // From login response - } -}); -``` - -## 2. Database API - -Direct JSON document storage with no database management required. - -### Get All Items -```javascript -// Replace :collectionName with your actual collection name (e.g., 'products') -const res = await fetch('https://api.urbackend.bitbros.in/api/data/products', { - headers: { 'x-api-key': 'YOUR_API_KEY' } -}); -const data = await res.json(); -``` - -### Insert Data -```javascript -await fetch('https://api.urbackend.bitbros.in/api/data/products', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'x-api-key': 'YOUR_API_KEY' - }, - body: JSON.stringify({ - name: "MacBook Pro", - price: 1299, - inStock: true - }) -}); -``` - -### Update / Delete by ID -```javascript -const id = "DOCUMENT_ID"; - -// Update -await fetch(`https://api.urbackend.bitbros.in/api/data/products/${id}`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - 'x-api-key': 'YOUR_API_KEY' - }, - body: JSON.stringify({ price: 1199 }) -}); - -// Delete -await fetch(`https://api.urbackend.bitbros.in/api/data/products/${id}`, { - method: 'DELETE', - headers: { 'x-api-key': 'YOUR_API_KEY' } -}); -``` - -## 3. Storage API - -Manage files and images with ease. - -### Upload File -```javascript -const formData = new FormData(); -formData.append('file', fileInput.files[0]); - -const res = await fetch('https://api.urbackend.bitbros.in/api/storage/upload', { - method: 'POST', - headers: { 'x-api-key': 'YOUR_API_KEY' }, - body: formData -}); -const data = await res.json(); -// Returns { url: "...", path: "project_id/filename.jpg" } -``` - -### Delete File -```javascript -await fetch('https://api.urbackend.bitbros.in/api/storage/file', { - method: 'DELETE', - headers: { - 'Content-Type': 'application/json', - 'x-api-key': 'YOUR_API_KEY' - }, - body: JSON.stringify({ - path: "PROJECT_ID/filename.jpg" - }) -}); -``` - -## πŸ” Security Best Practices - -> [!IMPORTANT] -> **Key Separation**: -> - Use the **Publishable Key** (`pk_live_...`) for frontend requests (Read-Only). -> - Use the **Secret Key** (`sk_live_...`) ONLY for backend/secure environments. diff --git a/DEVLOG.md b/DEVLOG.md deleted file mode 100644 index 653fc4b41..000000000 --- a/DEVLOG.md +++ /dev/null @@ -1,145 +0,0 @@ -# urBackend Development Log - -**urBackend**: [https://urbackend.bitbros.in](https://urbackend.bitbros.in) -**Github**: [https://github.com/yash-pouranik/urbackend](https://github.com/yash-pouranik/urbackend) - ---- - -## 16-01-2026 - Optimization: User Dashboard Projects Query - -### Context -On the user dashboard, I display a list of all projects owned by the logged-in user. The UI only requires the project name and description, but the backend query was returning the full Project document. - -### Original Issue -* Query fetched **all fields** from the Project model (β‰ˆ15 fields) -* Unnecessary data transfer and Mongoose document hydration -* Not optimal for a frequently hit dashboard endpoint - -### Changes Implemented -```javascript -const projects = await Project.find({ owner: req.user._id }) - .select('name description') - .lean(); -``` - -Additionally, I created an index: -```javascript -projectSchema.index({ owner: 1 }); -``` - -### Why This Improves Performance -* **`select('name description')`**: Reduces payload size and serialization cost -* **`.lean()`**: Skips Mongoose document hydration (plain JS objects) -* **Index on owner**: Avoids collection scans and significantly improves query speed - -### Key Learning -* `select()` optimizes data transfer, not query execution -* **Indexes** drive real query speed -* `.lean()` is critical for read-heavy, display-only endpoints - -### Result -* Faster dashboard load time -* Lower memory and CPU usage on the backend -* Cleaner and safer API response - ---- - -## 16-01-2026 - API Key Middleware Redis Caching - -### Context -The API-key authentication middleware runs on every incoming request. Originally, each request queried MongoDB to validate the API key and check the project owner’s verification status, making this a hot path and a potential performance bottleneck. - -### Problem -* Repeated MongoDB queries for the same API key -* Increased latency under high request volume -* Unnecessary load on the database for read-heavy authentication checks - -### Optimization Implemented -I introduced Redis caching for API-key validation. - -#### Cache Strategy -* **Redis String**: JSON-serialized object -* **Cache key**: `project:apikey:{hashedApiKey}` -* **Cached data**: - * `projectId` - * `owner.isVerified` - * Minimal project metadata required by middleware -* **TTL**: 2 hours - -#### Updated Flow -1. Hash incoming API key -2. Check Redis for cached project -3. On cache hit > skip MongoDB -4. On cache miss > query MongoDB, cache result, continue -5. Verify owner status before allowing request - -### Why Redis String -* Entire project object is read at once -* No partial updates required -* Simple GET / SET pattern -* Easy invalidation - -### Cache Invalidation Strategy -Cache is explicitly invalidated when: -* API key is rotated -* Project is deleted -* Project ownership or verification status changes - -### Key Learnings -* Middleware is a high-impact caching target -* `select()` optimizes payload size, not query execution -* Redis significantly reduces DB load for auth-heavy systems -* Correct invalidation is critical for security - -### Result -* Reduced authentication latency -* Lower MongoDB query volume -* More scalable API-key validation path - ---- - -## 17-01-2026 - Handling Write Operations with Redis - -### Context -After introducing Redis caching for API-key middleware, `req.project` started coming from Redis instead of MongoDB. Redis returns plain JavaScript objects, not hydrated Mongoose documents. - -### Problem -Some controllers (e.g., `insertData`) perform write operations on the Project model, such as updating `databaseUsed`. -When the project object came from Redis: -> `project.save is not a function` - -This happened because cached objects do not have Mongoose instance methods. - -### Initial Considerations -I evaluated multiple approaches: -* Skipping `.save()` (unsafe, loses consistency) -* Rehydrating cached objects into Mongoose documents (error-prone) -* Moving writes to background jobs (correct but premature) -* Refetching the document on every write (works but adds overhead) - -### Final Solution -I replaced the read-modify-write pattern with **atomic MongoDB updates**. - -```javascript -await Project.updateOne( - { _id: project._id }, - { $inc: { databaseUsed: docSize } } -); -``` - -### Why This Works -* Avoids document hydration -* Single database call -* Atomic and concurrency-safe -* Keeps MongoDB as the source of truth -* Cache remains strictly read-only - -### Key Learning -* Redis caching must be treated as a **read optimization**. -* All mutations should go through the database using atomic operations. - -### Result -* No runtime crashes -* Lower database overhead -* Clear separation between read and write paths -* More scalable quota enforcement logic diff --git a/README.md b/README.md index de2ccb293..ad4aece66 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,18 @@ +# urBackend πŸš€ +

urBackend Banner

+

- The Instant "Backend-as-a-Service" for Frontend Developers.
- Get a managed NoSQL database, JWT Auth, and Cloud Storage in 60 seconds. + Bring your own MongoDB. Get a production-ready backend in 60 seconds.
+ your backend β€” your database β€” your rules.

Dashboard Β· - Docs Β· + Docs Β· + Quick Start Β· Discord

@@ -27,12 +31,14 @@ urBackend is an **Open-Source BaaS** built to eliminate the complexity of backen ## 🟒 Powerful Features -- `>_` **Instant NoSQL Database**: Create collections and push JSON data instantly. -- `>_` **Managed Authentication**: Sign Up, Login, and Profile management with JWT. -- `>_` **Cloud Storage**: Managed file/image uploads with public CDN links. -- `>_` **Bring Your Own Database**: Connect your own MongoDB or Supabase instance. -- `>_` **Real-time Analytics**: Monitor traffic and resource usage from a premium dashboard. -- `>_` **Secure Architecture**: Dual-key separation (`pk_live` & `sk_live`) for total safety. +| Feature | Description | +| :--- | :--- | +| **Instant NoSQL** | Create collections and push JSON data instantly with zero boilerplate. | +| **Managed Auth** | Sign Up, Login, and Profile management with JWT built-in. | +| **Cloud Storage** | Managed file/image uploads with public CDN links. | +| **BYO Database** | Connect your own MongoDB Atlas or self-hosted instance. | +| **Real-time Analytics** | Monitor traffic and resource usage from a premium dashboard. | +| **Secure Architecture** | Dual-key separation (`pk_live` & `sk_live`) for total safety. | --- @@ -53,22 +59,26 @@ const res = await fetch('https://api.urbackend.bitbros.in/api/data/products', { --- -## πŸ› οΈ Infrastructure +## πŸ—οΈ How it Works (The Visual Flow) -
+```mermaid +graph LR + A[1. Connect MongoDB] --> B[2. Define Collections] + B --> C[3. πŸš€ Instant REST APIs] + C --> D[4. Scale & Monitor] +``` -| **Core System** | **Developer UI** | **Data Layer** | -| :--- | :--- | :--- | -| Node.js & Express | React.js (Vite) | MongoDB (Mongoose) | -| JWT Authentication | Lucide React | Redis & BullMQ | -| Storage Manager | Recharts | Supabase (BYOD) | +--- -
+## πŸ—οΈ Architecture ---- +Explore our [Architecture Diagram](ARCHITECTURE_DIAGRAM.md) to understand the system design, core components, and data flow in detail. +--- ## 🀝 Community +Join hundreds of developers building faster without the backend headaches. + - [GitHub Issues](https://github.com/yash-pouranik/urbackend/issues): Report bugs & request features. - [Discord Channel](https://discord.gg/CXJjvJkNWn): Join the conversation. - [Contributing](CONTRIBUTING.md): Help us grow the ecosystem. diff --git a/SUMMARY.md b/SUMMARY.md new file mode 100644 index 000000000..118d01cf1 --- /dev/null +++ b/SUMMARY.md @@ -0,0 +1,19 @@ +# Summary + +* [Summary](README.md) +* [API Quick Reference πŸ“‘](docs/api-reference.md) +* [Introduction πŸš€](docs/introduction.md) +* [Getting Started πŸ› οΈ](docs/getting-started.md) +* [Authentication πŸ”](docs/authentication.md) +* [Database Operations πŸ—„οΈ](docs/database.md) +* [Schema Creation 🧱](docs/schemas.md) +* [Cloud Storage ☁️](docs/storage.md) +* [Security & Keys πŸ›‘οΈ](docs/security.md) + +--- +## Project & Community 🀝 +* [Architecture Diagram](ARCHITECTURE_DIAGRAM.md) +* [Contributing](CONTRIBUTING.md) +* [Code of Conduct](CODE_OF_CONDUCT.md) +* [Security Policy](SECURITY.md) +* [License](LICENSE.md) diff --git a/backend/controllers/project.controller.js b/backend/controllers/project.controller.js index 78e864294..f729ac716 100644 --- a/backend/controllers/project.controller.js +++ b/backend/controllers/project.controller.js @@ -14,6 +14,7 @@ const { getCompiledModel } = require("../utils/injectModel") const QueryEngine = require("../utils/queryEngine"); const { storageRegistry } = require("../utils/registry"); const { deleteProjectByApiKeyCache, setProjectById, getProjectById, deleteProjectById } = require("../services/redisCaching"); +const { isProjectStorageExternal, isProjectDbExternal } = require("../utils/project.helpers"); const { v4: uuidv4 } = require('uuid'); const { getPublicIp } = require("../utils/network"); @@ -26,11 +27,6 @@ const validateUsersSchema = (schema) => { -const getBucket = (project) => - project.resources?.storage?.isExternal ? "files" : "dev-files"; - -const isExternalStorage = (project) => - !!project.resources?.storage?.isExternal; @@ -616,7 +612,7 @@ module.exports.uploadFile = async (req, res) => { .select("+resources.storage.config.encrypted +resources.storage.config.iv +resources.storage.config.tag resources.storage.isExternal storageUsed storageLimit"); if (!project) return res.status(404).json({ error: "Project not found" }); - const external = isExternalStorage(project); + const external = isProjectStorageExternal(project); if (!external) { if (project.storageUsed + file.size > project.storageLimit) { @@ -668,7 +664,7 @@ module.exports.deleteFile = async (req, res) => { const supabase = await getStorage(project); const bucket = getBucket(project); - const external = isExternalStorage(project); + const external = isProjectStorageExternal(project); let fileSize = 0; @@ -729,7 +725,7 @@ module.exports.deleteAllFiles = async (req, res) => { } } - if (!isExternalStorage(project)) { + if (!isProjectStorageExternal(project)) { project.storageUsed = 0; await project.save(); } @@ -822,7 +818,7 @@ module.exports.deleteProject = async (req, res) => { } // DELETE: Only for internal Infraa - if (!isExternalStorage(project)) { + if (!isProjectStorageExternal(project)) { const supabase = await getStorage(project); const bucket = getBucket(project); @@ -900,25 +896,19 @@ module.exports.toggleAuth = async (req, res) => { if (!project) return res.status(404).json({ error: "Project not found" }); if (enable) { - let usersCol = project.collections.find(c => c.name === 'users'); + const usersCol = project.collections.find(c => c.name === 'users'); if (!usersCol) { - usersCol = { - name: 'users', - model: [ - { key: 'email', type: 'String', required: true }, - { key: 'username', type: 'String', required: false }, - { key: 'password', type: 'String', required: true }, - { key: 'emailVerified', type: 'Boolean', required: false } - ] - }; - project.collections.push(usersCol); - } else { - if (!validateUsersSchema(usersCol.model)) { - return res.status(422).json({ - error: "Invalid Users Schema", - message: "The 'users' collection must have required 'email' and 'password' string fields. Please fix the schema before enabling Auth." - }); - } + return res.status(422).json({ + error: "Users Collection Missing", + message: "The 'users' collection must be created and configured with required 'email' and 'password' fields before enabling Authentication." + }); + } + + if (!validateUsersSchema(usersCol.model)) { + return res.status(422).json({ + error: "Invalid Users Schema", + message: "The 'users' collection must have required 'email' and 'password' string fields. Please fix the schema before enabling Auth." + }); } } diff --git a/backend/controllers/storage.controller.js b/backend/controllers/storage.controller.js index c68d8ef98..b1fd9861a 100644 --- a/backend/controllers/storage.controller.js +++ b/backend/controllers/storage.controller.js @@ -1,13 +1,12 @@ const { getStorage } = require("../utils/storage.manager"); const { randomUUID } = require("crypto"); +const Project = require("../models/Project"); +const { isProjectStorageExternal } = require("../utils/project.helpers"); const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB const getBucket = (project) => - project.resources?.storage?.isExternal ? "files" : "dev-files"; - -const isExternal = (project) => - !!project.resources?.storage?.isExternal; + isProjectStorageExternal(project) ? "files" : "dev-files"; /** * Upload File @@ -24,14 +23,21 @@ module.exports.uploadFile = async (req, res) => { } const project = req.project; - const external = isExternal(project); + const external = isProjectStorageExternal(project); const bucket = getBucket(project); + // ATOMIC QUOTA RESERVATION if (!external) { - if (project.storageUsed + file.size > project.storageLimit) { - return res - .status(403) - .json({ error: "Internal storage limit exceeded." }); + const result = await Project.updateOne( + { + _id: project._id, + $expr: { $lte: [{ $add: ["$storageUsed", file.size] }, "$storageLimit"] } + }, + { $inc: { storageUsed: file.size } } + ); + + if (result.matchedCount === 0) { + return res.status(403).json({ error: "Internal storage limit exceeded." }); } } @@ -47,11 +53,15 @@ module.exports.uploadFile = async (req, res) => { upsert: false }); - if (uploadError) throw uploadError; - - if (!external) { - project.storageUsed += file.size; - await project.save(); + if (uploadError) { + // ROLLBACK QUOTA + if (!external) { + await Project.updateOne( + { _id: project._id }, + { $inc: { storageUsed: -file.size } } + ); + } + throw uploadError; } const { data: publicUrlData } = supabase.storage @@ -86,7 +96,7 @@ module.exports.deleteFile = async (req, res) => { } const project = req.project; - const external = isExternal(project); + const external = isProjectStorageExternal(project); const bucket = getBucket(project); if (!path.startsWith(`${project._id}/`)) { @@ -117,11 +127,10 @@ module.exports.deleteFile = async (req, res) => { if (deleteError) throw deleteError; if (!external && fileSize > 0) { - project.storageUsed = Math.max( - 0, - project.storageUsed - fileSize + await Project.updateOne( + { _id: project._id }, + { $inc: { storageUsed: -fileSize } } ); - await project.save(); } return res.json({ message: "File deleted successfully" }); @@ -173,15 +182,17 @@ module.exports.deleteAllFiles = async (req, res) => { } // Reset usage only for internal storage - if (!isExternalStorage(project)) { - project.storageUsed = 0; - await project.save(); + if (!isProjectStorageExternal(project)) { + await Project.updateOne( + { _id: project._id }, + { $set: { storageUsed: 0 } } + ); } res.json({ success: true, deleted: deletedCount, - provider: isExternalStorage(project) ? "external" : "internal" + provider: isProjectStorageExternal(project) ? "external" : "internal" }); } catch (err) { diff --git a/backend/utils/project.helpers.js b/backend/utils/project.helpers.js new file mode 100644 index 000000000..fa1c0de11 --- /dev/null +++ b/backend/utils/project.helpers.js @@ -0,0 +1,26 @@ +/** + * Shared Project Helpers + */ + +/** + * Check if the project is configured to use external storage (Supabase BYOD) + * @param {Object} project - The project object (lean or document) + * @returns {Boolean} + */ +const isProjectStorageExternal = (project) => { + return !!project.resources?.storage?.isExternal; +}; + +/** + * Check if the project is configured to use an external database + * @param {Object} project - The project object + * @returns {Boolean} + */ +const isProjectDbExternal = (project) => { + return !!project.resources?.db?.isExternal; +}; + +module.exports = { + isProjectStorageExternal, + isProjectDbExternal +}; diff --git a/banner.png b/banner.png index 768e28cc6..7f7edc8d4 100644 Binary files a/banner.png and b/banner.png differ diff --git a/docs/api-reference.md b/docs/api-reference.md new file mode 100644 index 000000000..62321efe9 --- /dev/null +++ b/docs/api-reference.md @@ -0,0 +1,24 @@ +# API Quick Reference πŸ“‘ + +| Area | Method | Endpoint | Description | +| :--- | :--- | :--- | :--- | +| **Auth** | `POST` | `/api/userAuth/signup` | Register a new user | +| **Auth** | `POST` | `/api/userAuth/login` | Log in and get JWT | +| **Auth** | `GET` | `/api/userAuth/me` | Get current user profile | +| **Data** | `GET` | `/api/data/:collectionName` | Get all documents in collection | +| **Data** | `GET` | `/api/data/:collectionName/:id` | Get document by ID | +| **Data** | `POST` | `/api/data/:collectionName` | Insert new document | +| **Data** | `PUT` | `/api/data/:collectionName/:id` | Update document by ID | +| **Data** | `DELETE` | `/api/data/:collectionName/:id` | Delete document by ID | +| **Storage** | `POST` | `/api/storage/upload` | Upload a file | +| **Storage** | `DELETE` | `/api/storage/file` | Delete a file by path | + +## Status Code Reference + +- `200 OK`: Request succeeded. +- `201 Created`: Document/User/File created successfully. +- `400 Bad Request`: Validation failure or malformed JSON. +- `401 Unauthorized`: Missing/Invalid API Key or expired JWT. +- `403 Forbidden`: Resource limit (Quota) exceeded. +- `404 Not Found`: Collection, document, or file does not exist. +- `500 Server Error`: Unexpected problem on our end. diff --git a/docs/authentication.md b/docs/authentication.md new file mode 100644 index 000000000..ce21d429e --- /dev/null +++ b/docs/authentication.md @@ -0,0 +1,72 @@ +# Authentication πŸ” + +urBackend includes a built-in authentication system that manages user registration, login, and profile retrieval using **JSON Web Tokens (JWT)**. + +## The `users` Collection Contract + +To enable authentication, your project must have a collection named `users`. + +> [!IMPORTANT] +> **Schema Requirements**: +> The `users` collection **MUST** contain at least these two fields: +> 1. `email` (String, Required, Unique) +> 2. `password` (String, Required) +> +> You can add any other fields (e.g., `username`, `avatar`, `preferences`), and urBackend's Mongoose-powered validation will handle them automatically during signup. + +## 1. Sign Up User + +Creates a new user and returns a 7-day JWT token. + +**Endpoint**: `POST /api/userAuth/signup` + +```javascript +await fetch('https://api.urbackend.bitbros.in/api/userAuth/signup', { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'x-api-key': 'YOUR_KEY' }, + body: JSON.stringify({ + email: "dev@example.com", + password: "securePassword123", + username: "dev_pulse", + preferences: { theme: "dark", notifications: true } // Custom fields are supported! + }) +}); +``` + +## 2. Login User + +Authenticates credentials and returns a 7-day JWT token. + +**Endpoint**: `POST /api/userAuth/login` + +```javascript +const res = await fetch('https://api.urbackend.bitbros.in/api/userAuth/login', { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'x-api-key': 'YOUR_KEY' }, + body: JSON.stringify({ + email: "dev@example.com", + password: "securePassword123" + }) +}); +const { token } = await res.json(); +``` + +## 3. Get Profile (Me) + +Fetches the details of the currently authenticated user. + +**Endpoint**: `GET /api/userAuth/me` + +```javascript +await fetch('https://api.urbackend.bitbros.in/api/userAuth/me', { + headers: { + 'x-api-key': 'YOUR_KEY', + 'Authorization': `Bearer ${USER_TOKEN}` + } +}); +``` + +## Security Note + +- **JWT Expiration**: Tokens expire after **7 days**. Ensure your frontend handles token refresh or re-login logic. +- **Passwords**: Passwords are automatically hashed using **Bcrypt** before being stored. Even project owners cannot see raw user passwords. diff --git a/docs/database.md b/docs/database.md new file mode 100644 index 000000000..504a53a17 --- /dev/null +++ b/docs/database.md @@ -0,0 +1,64 @@ +# Database Operations πŸ—„οΈ + +urBackend provides a simplified RESTful interface for MongoDB. There is no need to write SQL or complex aggregation pipelinesβ€”just use simple JSON. + +## Collection Access + +All database endpoints follow the pattern: +`https://api.urbackend.bitbros.in/api/data/:collectionName` + +Replace `:collectionName` with the name of your collection (e.g., `posts`, `comments`, `inventory`). + +## 1. Create a Document + +**Endpoint**: `POST /api/data/:collectionName` + +```javascript +await fetch('https://api.urbackend.bitbros.in/api/data/posts', { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'x-api-key': 'YOUR_KEY' }, + body: JSON.stringify({ + title: "Why BaaS is the future", + body: "Content goes here...", + tags: ["tech", "development"], + meta: { views: 0, likes: 0 } + }) +}); +``` + +## 2. Read Documents + +### Fetch All +**Endpoint**: `GET /api/data/:collectionName` + +### Fetch Single Document +**Endpoint**: `GET /api/data/:collectionName/:id` + +## 3. Update a Document + +**Endpoint**: `PUT /api/data/:collectionName/:id` + +urBackend uses `$set` logic, meaning you only need to send the fields you want to change. + +```javascript +const postId = "YOUR_DOCUMENT_ID"; +await fetch(`https://api.urbackend.bitbros.in/api/data/posts/${postId}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json', 'x-api-key': 'YOUR_KEY' }, + body: JSON.stringify({ + "meta.views": 105 // Nested updates are supported! + }) +}); +``` + +## 4. Delete a Document + +**Endpoint**: `DELETE /api/data/:collectionName/:id` + +## Validation & Schemas + +If you define a schema in the dashboard, urBackend will enforce it for every `POST` and `PUT` request. + +- **Object Support**: You can define a field as an `Object` and send nested JSON. +- **Array Support**: Define a field as an `Array` for lists of data. +- **References (Ref)**: Link documents across collections by storing their `_id`. diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 000000000..78de9d638 --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,59 @@ +# Getting Started πŸ› οΈ + +**Go from an empty project to a production-ready backend in under 60 seconds.** + +urBackend is built for speed and developer freedom. Whether you're using our managed infrastructure or **Bringing Your Own MongoDB**, the setup experience is designed to be seamless. + +## 1. Create a Project + +Head over to the [urBackend Dashboard](https://urbackend.bitbros.in) and create a new project. You'll instantly receive two keys: + +- **Publishable Key (`pk_live_...`)**: Used for frontend requests (Read-Only). +- **Secret Key (`sk_live_...`)**: Used for server-side or administrative actions (Full Access). + +## 2. Your First Request + +Before pushing data, your **Collections must be pre-configured** in the dashboard. While urBackend offers flexible schema-less options, the collection itself must be registered so your API keys know where to route the data. + +To get started, navigate to the **Database** tab in your dashboard and click **"Create Collection"**. Specify a name (e.g., `products`) to initialize your endpoint. + +> **πŸ’‘ Pro Tip**: You can define a detailed schema during creation, or simply create the collection and start POSTing arbitrary JSON right awayβ€”urBackend will handle the rest. + +> **⚠️ Note**: Write operations (POST, PUT, DELETE) require your **Secret Key** and should only be performed from a secure backend environment, never from client-side code. + + +### Example: Storing a Product +```javascript +fetch('https://api.urbackend.bitbros.in/api/data/products', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'x-api-key': 'YOUR_SECRET_KEY' // Use secret key for write operations + }, + body: JSON.stringify({ + name: "Cyber Node Logo", + category: "Graphics", + premium: true + }) +}) +.then(res => res.json()) +.then(data => console.log("Product saved:", data)); +``` + +## 3. Define Your Models (Recommended) + +While urBackend is flexible, we recommend defining **Schemas** in the dashboard. This ensures data integrity and enables features like: +- **Type Checking**: (e.g., ensuring `price` is always a Number). +- **Required Fields**: Preventing incomplete documents from being saved. +- **Complex Types**: Using Objects, Arrays, and References properly. + +## 4. Environment Setup + +Store your keys in an `.env` file for safety: + +```env +VITE_URBACKEND_KEY=pk_live_xxxxxx +URBACKEND_SECRET=sk_live_yyyyyy +``` + +Now you're ready to dive into [Authentication](authentication.md) or [Database Management](database.md)! diff --git a/docs/introduction.md b/docs/introduction.md new file mode 100644 index 000000000..b5ad7cc05 --- /dev/null +++ b/docs/introduction.md @@ -0,0 +1,24 @@ +# Introduction πŸš€ + +**Bring your own MongoDB. Get a production-ready backend in 60 seconds.** + +> **your backend β€” your database β€” your rules.** + +urBackend is a high-performance, open-source **Backend-as-a-Service (BaaS)** designed to eliminate the friction of building and managing server-side infrastructure without the trap of vendor lock-in. + +## Why urBackend? + +Traditional BaaS platforms lock you into their database and proprietary APIs. urBackend is different. It's built on the principle of **BYOD (Bring Your Own Database)**. You keep your data in your own hosted MongoDB instance, while we provide the professional-grade API layer, Auth, and Storage to make it production-ready instantly. + +- 🟒 **Instant NoSQL Database**: Create collections visually and interact with them via a standard REST API. +- πŸ” **Managed Authentication**: Built-in logic for Sign Up, Login, and Profile management with secure JWT tokens. +- πŸ“¦ **Cloud Storage**: Seamlessly upload files and images with public CDN links automatically generated. +- πŸ”Œ **Dynamic Schemas**: Define your data structures using a simple visual modeler with Mongoose-powered validation. +- πŸ›‘οΈ **Advanced Security**: Integrated rate limiting, NoSQL injection protection, and dual-key separation (`pk_live` & `sk_live`). +- ☁️ **BYOD (Bring Your Own Database)**: Connect your own MongoDB or Supabase instance if you need total data control. + +## The Problem We Solve + +Traditional backend development is slow. You have to setup servers, configure databases, write boilerplate auth logic, and manage deployments. + +urBackend provides a **Unified REST API** that abstracts all that complexity away. You focus on your UI and user experience; we handle the pulse of your data. diff --git a/docs/schemas.md b/docs/schemas.md new file mode 100644 index 000000000..00f30766e --- /dev/null +++ b/docs/schemas.md @@ -0,0 +1,60 @@ +# Schema Creation & Types 🧱 + +urBackend is powered by a dynamic schema engine. You can define your data structure visually in the dashboard, and our API will automatically enforce validation, provide type safety, and even generate your Admin UI. + +## Supported Data Types + +Every field in your collection must have a type. Here are the types urBackend supports: + +| Type | Description | Example JSON | +| :--- | :--- | :--- | +| **String** | Alphanumeric text data. | `"title": "Hello World"` | +| **Number** | Integers or decimals. | `"price": 19.99` | +| **Boolean** | `true` or `false` values. | `"isActive": true` | +| **Date** | Any valid date or ISO string. | `"createdAt": "2024-03-07"` | +| **Object** | Nested JSON structure. | `"meta": { "views": 10 }` | +| **Array** | A list of values. | `"tags": ["tech", "ai"]` | +| **Ref** | Reference to another document ID. | `"author": "642f9..."` | + +## 1. Required Fields ⚠️ + +When you toggle a field as **Required** in the dashboard, the API will reject any `POST` or `PUT` request that doesn't include that field. This ensures your database always has the data your application needs. + +## 2. Nested Objects πŸ“¦ + +You can create complex, hierarchical data structures by using the **Object** type. +- In the Dashboard: Add a field, set type to `Object`, then add "Sub-fields" inside it. +- In the API: Send a normal nested JSON object. + +```json +{ + "profile": { + "avatar": "url...", + "bio": "Developer at heart" + } +} +``` + +## 3. Arrays πŸ“Š + +The **Array** type allows you to store lists of values. +- In the Dashboard: Set type to `Array`. +- In the API: Send a standard JSON array `[]`. + +```json +{ + "categories": ["electronics", "smartphones", "deals"] +} +``` + +## 4. References (Ref/Lookup) πŸ”— + +References allow you to link documents between collections (similar to foreign keys or Joins). +- **Setup**: Set type to `Ref` and choose the target collection. +- **Usage**: Store the `_id` of the document you want to link to. +- **Benefit**: This enables you to build relational data structures while keeping the flexibility of NoSQL. + +--- + +> [!TIP] +> Always define your **"users"** collection schema manually before enabling Authentication to ensure your custom user fields (like `avatar` or `role`) are properly validated. diff --git a/docs/security.md b/docs/security.md new file mode 100644 index 000000000..a0ea38066 --- /dev/null +++ b/docs/security.md @@ -0,0 +1,33 @@ +# Security & Keys πŸ›‘οΈ + +**your backend β€” your database β€” your rules.** + +urBackend is built with a "Security-First" philosophy, specifically designed for developers who want full control over their data without sacrificing safety. + +## Dual-Key System + +We use two distinct types of API keys to prevent accidental data leaks: + +| Key Type | Prefix | Shared Environment | Access Level | +| :--- | :--- | :--- | :--- | +| **Publishable** | `pk_live_` | Frontend / Client | Read-Only (Safe if leaked) | +| **Secret** | `sk_live_` | Backend / Server | Full Access (CRUD) | + +> [!CAUTION] +> **NEVER** commit your Secret Key to version control (GitHub/GitLab) or use it in frontend code. + +## Protection Mechanisms + +### 1. NoSQL Injection Prevention +Our API sanitizes top-level incoming JSON keys that start with `$`. Nested objects should still be validated carefully until recursive sanitization is added. + +### 2. Rate Limiting +To prevent DDoS attacks and brute-force attempts: +- **Global API**: Limited to **100 requests per 15 minutes** per IP. +- **Auth Endpoints**: Protected by a stricter per-IP request limit. + +### 3. Domain Whitelisting +In your dashboard, you can restrict API access to specific domains. When enabled, urBackend will reject any request that doesn't originate from your allowed list. + +### 4. Schema Enforcement +When you define a schema, urBackend uses **Mongoose Model Validation** to ensure no "dirty" or unexpected data is saved to your database. diff --git a/docs/storage.md b/docs/storage.md new file mode 100644 index 000000000..933f0b790 --- /dev/null +++ b/docs/storage.md @@ -0,0 +1,49 @@ +# Cloud Storage ☁️ + +Easily manage file and image uploads without setting up AWS S3 buckets or managing complex multi-part forms. + +## 1. Upload a File + +To upload a file, send a `POST` request with a `multipart/form-data` body. + +**Endpoint**: `POST /api/storage/upload` + +```javascript +const formData = new FormData(); +formData.append('file', fileInput.files[0]); + +const res = await fetch('https://api.urbackend.bitbros.in/api/storage/upload', { + method: 'POST', + headers: { 'x-api-key': 'YOUR_KEY' }, + body: formData +}); + +const { url, path } = await res.json(); +// url: "https://storage.urbackend.bitbros.in/.../image.jpg" +// path: "project_id/image.jpg" +``` + +> [!NOTE] +> Do **NOT** set the `Content-Type` header manually for file uploads; the browser will handle it for you when you pass a `FormData` object. + +## 2. Delete a File + +To delete a file, you must provide the `path` returned during the upload. + +**Endpoint**: `DELETE /api/storage/file` + +```javascript +await fetch('https://api.urbackend.bitbros.in/api/storage/file', { + method: 'DELETE', + headers: { 'Content-Type': 'application/json', 'x-api-key': 'YOUR_KEY' }, + body: JSON.stringify({ + path: "YOUR_PROJECT_ID/your_file_name.png" + }) +}); +``` + +## Limits + +- **File Size**: Maximum **10 MB** per file. +- **Storage Quota**: Total storage depends on your project's plan (default: **100 MB**). +- **Public Access**: All uploaded files are publicly accessible via the returned URL. Do not upload sensitive, private documents. diff --git a/frontend/public/logo.png b/frontend/public/logo.png index 5dec8394f..6147844c3 100644 Binary files a/frontend/public/logo.png and b/frontend/public/logo.png differ diff --git a/frontend/public/logo_u.png b/frontend/public/logo_u.png deleted file mode 100644 index b5ed8a22b..000000000 Binary files a/frontend/public/logo_u.png and /dev/null differ diff --git a/frontend/src/components/Layout/Footer.jsx b/frontend/src/components/Layout/Footer.jsx index e39bb6a2e..55e5821b2 100644 --- a/frontend/src/components/Layout/Footer.jsx +++ b/frontend/src/components/Layout/Footer.jsx @@ -12,9 +12,8 @@ export default function Footer() { {/* Brand / Newsletter Column */}
-
- - urBackend +
+ urBackend Logo

The instant Backend-as-a-Service for frontend developers. Ship faster. diff --git a/frontend/src/components/Layout/MainLayout.jsx b/frontend/src/components/Layout/MainLayout.jsx index 8df9e4e4a..7c528788b 100644 --- a/frontend/src/components/Layout/MainLayout.jsx +++ b/frontend/src/components/Layout/MainLayout.jsx @@ -3,7 +3,8 @@ import { useLocation, matchPath } from 'react-router-dom'; import Sidebar from './Sidebar'; import Header from './Header'; import ProjectNavbar from './ProjectNavbar'; -import logoImage from '../../assets/logo_u.png'; +// Use the new official logo from public directory +const logoImage = "/logo.png"; function MainLayout({ children }) { const [isSidebarOpen, setIsSidebarOpen] = useState(false); diff --git a/frontend/src/components/Layout/Sidebar.jsx b/frontend/src/components/Layout/Sidebar.jsx index 2b8e3b79b..f9e9cc8f1 100644 --- a/frontend/src/components/Layout/Sidebar.jsx +++ b/frontend/src/components/Layout/Sidebar.jsx @@ -27,8 +27,7 @@ function Sidebar({ logo, isOpen, onClose }) { // Props received ) : (

- Logo - urBackend + urBackend Logo
)} diff --git a/frontend/src/pages/Auth.jsx b/frontend/src/pages/Auth.jsx index 2f22f9ad7..22c01dc2c 100644 --- a/frontend/src/pages/Auth.jsx +++ b/frontend/src/pages/Auth.jsx @@ -270,7 +270,7 @@ export default function Auth() { ); setEditingUser(res.data); const customFields = { ...res.data }; - ['_id', 'email', 'password', 'emailVerified', 'createdAt', 'updatedAt'].forEach(key => { delete customFields[key]; }); + ['_id', 'password', 'emailVerified', 'createdAt', 'updatedAt'].forEach(key => { delete customFields[key]; }); setEditFormData(customFields); } catch { toast.error("Failed to fetch user details"); diff --git a/frontend/src/pages/LandingPage/index.jsx b/frontend/src/pages/LandingPage/index.jsx index d6d3243a6..e89bd1617 100644 --- a/frontend/src/pages/LandingPage/index.jsx +++ b/frontend/src/pages/LandingPage/index.jsx @@ -114,9 +114,8 @@ function LandingPage() {