Thank you for your interest in contributing to Blob! This guide will help you get started, even if you're new to open source or programming.
- Getting Started
- Development Environment Setup
- Running the Project Locally
- Making Changes
- Submitting Your Contribution
- Code Style Guidelines
- Troubleshooting
- Getting Help
Before you start, make sure you have these essential tools installed on your system:
- What it does: Tracks code changes and manages different versions of your code
- Why you need it: To clone the repository and submit your contributions
- Download: git-scm.com/downloads
- Verify installation: Run
git --versionin your terminal - Expected output:
git version 2.x.xor higher
- What it does: Executes JavaScript code outside the browser
- Why you need it: Powers the API server, build tools, and all development scripts
- Minimum version required: 18 or higher
- Download: nodejs.org - Get the LTS (Long Term Support) version
- Verify installation: Run
node --versionin your terminal - Expected output:
v18.x.xor higher
- What it does: Installs and manages JavaScript packages/dependencies
- Why you need it: This project uses pnpm for efficient monorepo workspace management
- Minimum version required: 8 or higher
- Install: Run
npm install -g pnpm(after installing Node.js) - Verify installation: Run
pnpm --versionin your terminal - Expected output:
8.x.xor9.x.x - Why pnpm and not npm?: Faster installs, better disk space usage, and monorepo support
- What it does: Runs applications in isolated containers
- Why you need it: Provides a local PostgreSQL database for development
- Download: docker.com/products/docker-desktop
- Verify installation: Run
docker --versionin your terminal - Expected output:
Docker version 20.x.xor higher - IMPORTANT: Make sure Docker Desktop is running (not just installed) before starting development
- How to check: Run
docker ps- should not show "Cannot connect to Docker daemon"
- What it does: Allows you to test the mobile app on your actual phone
- Why you need it: To see your changes in real-time on a mobile device
- Download:
- iOS: App Store
- Android: Play Store
- Install this on your phone, not your computer!
- Alternative: You can use Android Studio Emulator or iOS Simulator (Mac only) instead
- What it does: Where you'll write and edit code
- Recommendations:
- Neovim: neovim.io - Lightweight, powerful, makes you look cool
- VS Code: code.visualstudio.com - Beginner-friendly, feature-rich
- Any editor you prefer works fine!
- GitHub Desktop: If you prefer a GUI for Git instead of command line - desktop.github.com
- Bun Runtime: Alternative to Node.js for production builds - bun.sh (optional, only needed for production deployment)
- Android Studio: For Android emulator - developer.android.com/studio
- Xcode: For iOS simulator (Mac only) - Available on Mac App Store
Run these commands to verify everything is installed correctly:
# Check Node.js (should show v18 or higher)
node --version
# Check pnpm (should show 8.x or higher)
pnpm --version
# Check Git
git --version
# Check Docker (should show version number)
docker --version
# Check if Docker is running (should list containers or show empty list)
docker psIf any command fails or shows a lower version, install or update that tool before proceeding.
-
Fork the repository
- Go to https://github.com/opencodeiiita/blob
- Click the "Fork" button in the top right
- This creates a copy of the project in your GitHub account
-
Clone your fork
git clone https://github.com/<your-username>/blob.git cd blob
We provide an automated setup script that handles the entire development environment setup for you:
pnpm setupThis script will:
- Check all prerequisites (Node.js, pnpm, Docker, Git)
- Install all dependencies
- Create environment files (.env and .dev.vars)
- Start Docker containers (PostgreSQL)
- Set up the database schema
The script is fully interactive and cross-platform (Windows, macOS, Linux).
Alternatively, you can follow the manual setup steps below:
pnpm installThis command will install all the necessary packages for the project. It might take a few minutes.
Create a .env file in the root directory:
cp .env.example .envEdit the .env file. For local development with Docker, use:
# Database (local development with Docker)
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/blob
# For production with Neon Postgres, replace with your Neon connection string:
# DATABASE_URL=postgresql://user:password@host/databaseCreate a .env file in the apps/server directory:
cd apps/server
cp .env.example .envThe .env file should contain the same database URL:
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/blob
Note: You don't need to sign up for Neon Postgres for local development! We'll use Docker instead (see next step).
Start the PostgreSQL database using Docker:
pnpm docker:upThis command will:
- Download the PostgreSQL Docker image (first time only)
- Start a local PostgreSQL database on port 5432
- Create a database named
blob
To check if the database is running:
docker psYou should see a container named blob-postgres running.
Useful Docker commands:
# Stop the database
pnpm docker:down
# View database logs
pnpm docker:logs
# Restart the database
pnpm docker:restartPush the database schema to your local database:
pnpm db:pushThis creates all the necessary tables in your PostgreSQL database.
Other useful database commands:
# Generate migrations
pnpm db:generate
# Run migrations
pnpm db:migrate
# Open Drizzle Studio (database GUI)
pnpm db:studioIMPORTANT: You need to allow these ports through your firewall for the app to work:
- Port 5432 - PostgreSQL database (if accessing from other machines)
- Port 8081 - Expo development server
- Port 8787 - API server (Hono/Cloudflare Workers)
- Open "Windows Defender Firewall"
- Click "Advanced settings"
- Click "Inbound Rules" → "New Rule"
- Select "Port" → Next
- Enter "5432, 8081, 8787" → Next
- Select "Allow the connection" → Next
- Check all network types → Next
- Name it "Blob Development" → Finish
macOS typically allows local network connections by default. If you have issues:
- Go to System Preferences → Security & Privacy → Firewall
- Click "Firewall Options"
- Ensure Node, Expo, and Docker are allowed
sudo ufw allow 5432
sudo ufw allow 8081
sudo ufw allow 8787sudo firewall-cmd --permanent --add-port=5432/tcp
sudo firewall-cmd --permanent --add-port=8081/tcp
sudo firewall-cmd --permanent --add-port=8787/tcp
sudo firewall-cmd --reloadYou'll need to run both the API server and the mobile app at the same time. Turbo handles it for you, say thanks to turbo:
pnpm devThis will start:
- API Server on
http://0.0.0.0:8787(Node.js runtime with hot reload) - Mobile App with Expo development server
The API server uses different runtimes for different environments:
- Development: Node.js with tsx (hot reload, better debugging, familiar tooling)
- Production: Bun (faster startup, native performance, optimized for production)
The server automatically detects which runtime it's running on and adapts accordingly. You don't need to do anything special - just run pnpm dev for development!
Why different runtimes?
- Node.js is better for development (mature tooling, extensive debugging support)
- Bun is better for production (faster execution, lower memory usage)
- Open Expo Go app on your phone
- Make sure your phone is on the same Wi-Fi network as your computer
- Scan the QR code:
- iOS: Use the Camera app
- Android: Use the Expo Go app to scan
The app should load on your phone! Any changes you make to the code will automatically reload.
If you prefer to use an emulator instead of your phone:
Android Emulator:
# In the mobile terminal, press 'a'iOS Simulator (Mac only):
# In the mobile terminal, press 'i'blob/
├── apps/
│ ├── server/ # Backend API server
│ │ ├── src/
│ │ └── index.ts # Main API entry point
│ │
│ └── mobile/ # Mobile app
│ ├── app/ # App screens (file-based routing)
│ ├── components/ # Reusable UI components
│ ├── providers/ # React context providers
│ ├── store/ # State management (Zustand)
│ └── utils/ # Helper functions
│
├── packages/
│ ├── db/ # Database package (Drizzle ORM)
│ │ ├── src/
│ │ │ ├── schema/ # Database schema definitions
│ │ │ └── index.ts # Database client
│ │ └── drizzle.config.ts
│ │
│ └── api/ # Shared API types and router
│ ├── src/
│ │ ├── routers/ # API route definitions
│ │ ├── server.ts # tRPC server setup
│ │ └── client.ts # tRPC client setup
│
├── docker-compose.yml # Local database configuration
└── docs/ # Documentation
- Open
packages/api/src/routers/index.ts - Add your new route:
export const appRouter = router({
// Existing routes...
// Your new route
createFlashcard: publicProcedure
.input(
z.object({
topic: z.string(),
content: z.string(),
}),
)
.mutation(async ({ input }) => {
// Your logic here
return { success: true, id: "123" };
}),
});- The route is now available in the mobile app automatically!
import { trpc } from '@/providers/trpc';
export default function MyScreen() {
const createFlashcard = trpc.createFlashcard.useMutation();
const handleCreate = async () => {
const result = await createFlashcard.mutateAsync({
topic: 'Math',
content: 'What is 2+2?',
});
console.log(result);
};
return (
<Button onPress={handleCreate}>
Create Flashcard
</Button>
);
}-
Create a new file in
apps/mobile/app/:// apps/mobile/app/flashcards.tsx import { View, Text } from 'react-native'; export default function FlashcardsScreen() { return ( <View> <Text>Flashcards</Text> </View> ); }
-
Navigate to it from another screen:
import { router } from 'expo-router'; <Button onPress={() => router.push('/flashcards')}> Go to Flashcards </Button>
Create components in apps/mobile/components/:
// apps/mobile/components/FlashCard.tsx
import { View, Text } from 'react-native';
interface FlashCardProps {
question: string;
answer: string;
}
export function FlashCard({ question, answer }: FlashCardProps) {
return (
<View>
<Text>{question}</Text>
<Text>{answer}</Text>
</View>
);
}-
Edit your schema in
packages/db/src/schema/(e.g., create a new file for your table)// packages/db/src/schema/flashcards.ts import { pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"; export const flashcards = pgTable("flashcards", { id: uuid("id").defaultRandom().primaryKey(), question: text("question").notNull(), answer: text("answer").notNull(), createdAt: timestamp("created_at").defaultNow().notNull(), });
-
Export the schema in
packages/db/src/schema/index.ts:export * from "./users"; export * from "./flashcards";
-
Push the changes to your database:
pnpm db:push
Using the database in your code:
import { db } from "@repo/db";
import { flashcards } from "@repo/db/schema";
// Insert a flashcard
await db.insert(flashcards).values({
question: "What is 2+2?",
answer: "4",
});
// Query flashcards
const allCards = await db.select().from(flashcards);Always create a new branch for your changes:
git checkout -b feature/your-feature-nameBranch naming conventions:
feature/- New features (e.g.,feature/add-flashcards)fix/- Bug fixes (e.g.,fix/login-error)docs/- Documentation changes (e.g.,docs/update-readme)
- Write clean, readable code
- Test your changes thoroughly
- Follow the code style guidelines below
git add .
git commit -m "Add flashcard generation feature"Commit message guidelines:
- Use present tense ("Add feature" not "Added feature")
- Be descriptive but concise
- Start with a verb (Add, Fix, Update, Remove, etc.)
Good examples:
- "Add flashcard generation with AI"
- "Fix login button not responding"
- "Update README with setup instructions"
git push origin feature/your-feature-name-
Go to your fork on GitHub
-
Click "Compare & pull request"
-
Fill out the PR template:
- Describe what changes you made
- Explain why you made them
- Add screenshots if you changed the UI
-
Make sure to add the issue number by adding
issue: #<issue-number>, real world doesn't work that way it's just opencode's protocols for points distribution. -
Create pull request
- A maintainer will review your code
- They might suggest changes
- Make any requested changes and push them to your branch
- Once approved, your code will be merged!
- Be consistent - Follow the existing code style
- Be clear - Use descriptive variable and function names
- Be simple - Don't over-complicate things
- Comment when necessary - Explain "why", not "what"
// Good: Descriptive names, proper types
interface FlashcardData {
question: string;
answer: string;
difficulty: "easy" | "medium" | "hard";
}
async function generateFlashcard(topic: string): Promise<FlashcardData> {
// Implementation
}
// Bad: Unclear names, missing types
function gen(t: any): any {
// Implementation
}// Good: Props interface, functional component
interface ButtonProps {
title: string;
onPress: () => void;
disabled?: boolean;
}
export function Button({ title, onPress, disabled = false }: ButtonProps) {
return (
<Pressable onPress={onPress} disabled={disabled}>
<Text>{title}</Text>
</Pressable>
);
}
// Bad: No types, unclear purpose
export function Btn(props: any) {
return <Pressable onPress={props.p}><Text>{props.t}</Text></Pressable>;
}- Variables & Functions:
camelCase(e.g.,generateFlashcard) - Components:
PascalCase(e.g.,FlashCard) - Constants:
UPPER_SNAKE_CASE(e.g.,MAX_FLASHCARDS) - Files: Match the export (e.g.,
FlashCard.tsxforFlashCardcomponent)
// 1. Imports - external first, then internal
import React from 'react';
import { View, Text } from 'react-native';
import { Button } from '@/components/Button';
import { trpc } from '@/providers/trpc';
// 2. Types/Interfaces
interface MyComponentProps {
title: string;
}
// 3. Component
export function MyComponent({ title }: MyComponentProps) {
// 4. Hooks
const [state, setState] = React.useState('');
// 5. Event handlers
const handlePress = () => {
setState('pressed');
};
// 6. Render
return (
<View>
<Text>{title}</Text>
<Button onPress={handlePress} />
</View>
);
}We use Prettier for code formatting. It runs automatically before commits.
To format manually:
pnpm formatTo check formatting:
pnpm format:check- Make sure the API server is running (
cd apps/server && pnpm dev) - Check that ports 8081 and 8787 are open in your firewall
- Ensure your phone and computer are on the same Wi-Fi network
- Try restarting both servers
# Clear all caches and reinstall
rm -rf node_modules
rm -rf apps/*/node_modules
rm pnpm-lock.yaml
pnpm install- Make sure you're on the same Wi-Fi network
- Try scanning the QR code again
- Check if your firewall is blocking the connection
- Restart the Expo development server (press
rin the terminal) - Contact
@07calc
- Make sure Docker is running:
docker ps - Check if the database container is up:
pnpm docker:logs - Verify your
DATABASE_URLin the root.envfile - Restart the database:
pnpm docker:restart - Try running migrations again:
pnpm db:push
If using Neon Postgres (production):
- Check your
DATABASE_URLconnection string - Make sure your Neon database is running
- Verify network connectivity to Neon
# Rebuild types
cd packages/api
pnpm buildIf you see "port 8787 already in use":
# Find and kill the process (Mac/Linux)
lsof -ti:8787 | xargs kill -9
# Windows
netstat -ano | findstr :8787
taskkill /PID <PID> /FIf you're stuck, gather this information when asking for help:
# System info
node --version
pnpm --version
git --version
# Error logs
cd apps/server
pnpm dev > server-error.log 2>&1
cd apps/mobile
pnpm start > mobile-error.log 2>&1- Questions? Open a Discussion
- Found a bug? Open an Issue
- Want to chat? Join opencodeiiita community
Be kind, respectful, and inclusive. We're all here to learn and build something great together.
Thank you to all our contributors!
By contributing to Blob, you agree that your contributions will be licensed under the MIT License.
Happy coding! Remember, everyone was a beginner once. Don't be afraid to ask questions or make mistakes - that's how we all learn!