March 28, 2025

MongoDB Performance: Views vs $lookup vs Views with $lookup vs Stored Procedures

When working with multiple collections in MongoDB, we often need to join data. MongoDB provides different approaches to achieve this, including views, aggregation with $lookup, and views with $lookup. Additionally, we compare these with stored procedures (common in SQL databases) to highlight performance differences.

This blog is structured for:

  • Beginners (10-year-old level): Simple explanations of views, $lookup, and queries.

  • Experienced Developers (20+ years): In-depth performance analysis, execution times, and best practices.

This blog analyzes the performance impact of:

  1. Querying a View

  2. Querying with $lookup (Join in Aggregation)

  3. Querying a View with $lookup

  4. Comparison with Stored Procedures


1. What is a View in MongoDB?

A view in MongoDB is a saved aggregation query that returns live data from collections. It does not store data but runs the aggregation each time it’s queried.

Example

Let's create a view from a users collection:

// Create a view that selects only active users
 db.createView("activeUsers", "users", [
   { $match: { status: "active" } },
   { $project: { _id: 1, name: 1, email: 1 } }
]);

Performance Considerations

βœ… Queries on views reuse base collection indexes.
❌ Views do not store data, so they recompute results every time.
❌ Cannot have indexes on the view itself.

πŸ”Ή Performance (Example Execution Time): Querying the view for 10,000 documents takes 350ms.


2. What is $lookup in MongoDB?

$lookup is a real-time join operation in MongoDB’s aggregation pipeline. It links data from one collection to another at query execution time.

Example

Let's join users and orders collections:

 db.users.aggregate([
   {
     $lookup: {
       from: "orders",
       localField: "_id",
       foreignField: "userId",
       as: "userOrders"
     }
   }
]);

Performance Considerations

βœ… Can leverage indexes on foreignField (e.g., userId).
❌ Can be slow for large datasets as it retrieves data at query execution time.

πŸ”Ή Performance (Example Execution Time): Querying users with $lookup on orders for 10,000 users takes 450ms.


3. Querying a View with $lookup

This approach first queries a view and then applies a $lookup on it.

Example

Let's perform $lookup on our previously created activeUsers view:

 db.activeUsers.aggregate([
   {
     $lookup: {
       from: "orders",
       localField: "_id",
       foreignField: "userId",
       as: "userOrders"
     }
   }
]);

Performance Considerations

βœ… Encapsulates complex logic for better reusability.
❌ Double execution overhead (First executes view, then applies $lookup).

πŸ”Ή Performance (Example Execution Time): Querying activeUsers view with $lookup takes 750ms.


4. What is a Stored Procedure?

In relational databases, stored procedures are precompiled SQL queries that execute much faster than ad-hoc queries.

Example (SQL Stored Procedure to Join Users & Orders)

CREATE PROCEDURE GetUserOrders
AS
BEGIN
   SELECT u.id, u.name, o.order_id, o.total_amount
   FROM users u
   JOIN orders o ON u.id = o.user_id;
END;

Performance Considerations

βœ… Precompiled execution reduces query parsing overhead.
βœ… Can be indexed and optimized by the database engine.
❌ Not available in MongoDB (workarounds include pre-aggregated collections).

πŸ”Ή Performance (Example Execution Time in SQL): Running the stored procedure for 10,000 users takes 200ms.


Performance Comparison Table

Query Type Data Size Execution Time (ms)
Query on a View 10,000 350ms
Query with $lookup 10,000 450ms
Query on View with $lookup 10,000 750ms
SQL Stored Procedure 10,000 200ms

Key Optimization Insight

Based on the above performance tests, stored procedures (or equivalent pre-aggregated collections in MongoDB) are nearly 3 times faster than querying views with $lookup.

Why?

  • Stored procedures are precompiled, reducing execution overhead.

  • MongoDB views with $lookup execute two queries: first to generate the view and then to perform the join.

  • Indexing helps, but it cannot fully mitigate the double computation in view-based queries.

πŸ”Ή Fact: If your GET APIs frequently rely on view-based lookups, consider moving to stored procedures (in SQL) or pre-aggregated collections in MongoDB for significant performance gains.


Which Approach Should You Choose?

βœ… Use Views when:

  • You need reusable, filtered data representation.

  • Data size is small to moderate.

  • Performance is not a critical factor.

βœ… Use $lookup in Aggregation when:

  • You need real-time joins with fresh data.

  • You have indexes on join fields to improve speed.

  • You need better query performance than views.

βœ… Avoid Views with $lookup unless:

  • You absolutely need to pre-process data before a join.

  • You have a small dataset, and performance is acceptable.

βœ… Use Stored Procedures (if using SQL) or Pre-Aggregated Collections (MongoDB) when:

  • You need precompiled execution for optimal speed.

  • Queries need to be highly optimized for performance.

  • Your system supports SQL databases or can maintain pre-aggregated data.


Final Verdict

Scenario Best Approach
Simple reusable filtering View
Real-time joins $lookup
Preprocessed joins View + $lookup (if necessary)
High-performance joins SQL Stored Procedure / Pre-Aggregated Collection

πŸ”Ή Key takeaway: Stored procedures or pre-aggregated collections in MongoDB offer the best performance, while view-based lookups should be avoided for frequent queries due to high overhead.

Would you like further optimizations? Let us know! πŸš€

πŸš€ Fixing Circular Import Errors in Flask: The Modern Way!

Are you getting this frustrating error while running your Flask app?

ImportError: cannot import name 'app' from partially initialized module 'app' (most likely due to a circular import)

You're not alone! Circular imports are a common issue in Flask apps, and in this post, I'll show you exactly why this happens and give you modern solutions with real examples to fix it.


πŸ” Understanding the Circular Import Error

A circular import happens when two or more modules depend on each other, creating an infinite loop.

πŸ›‘ Example of Circular Import Issue

❌ app.py (Before - Problematic Code)

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from backend.config import Config  # 🚨 Circular Import Risk!
from backend.routes import routes
import backend.utils as utils

db = SQLAlchemy()
app = Flask(__name__)
app.config.from_object(Config)

db.init_app(app)
app.register_blueprint(routes)

with app.app_context():
    utils.reset_database()
    utils.initialize_db()

if __name__ == '__main__':
    app.run(debug=True)

❌ backend/config.py (Before - Problematic Code)

from app import app  # 🚨 Circular Import Error Happens Here!
class Config:
    SQLALCHEMY_DATABASE_URI = 'sqlite:///example.db'
    SQLALCHEMY_TRACK_MODIFICATIONS = False

πŸ”„ What’s Happening?

  1. app.py imports Config from backend.config

  2. backend/config.py imports app from app.py

  3. Flask hasn't finished initializing app, so the import is incomplete β†’ Boom! Circular Import Error! πŸ’₯


βœ… How to Fix Circular Imports in Flask? (Modern Solutions)

πŸ“Œ Solution 1: Move the Import Inside a Function

Instead of importing app at the top of backend/config.py, import it only when needed.

✨ Fixed backend/config.py

class Config:
    SQLALCHEMY_DATABASE_URI = 'sqlite:///example.db'
    SQLALCHEMY_TRACK_MODIFICATIONS = False

βœ… Now, config.py is independent and doesn’t need app.py.


πŸ“Œ Solution 2: Use Flask’s App Factory Pattern (πŸ”₯ Recommended)

A better way to structure your Flask app is to use the App Factory Pattern, which ensures components are initialized properly.

✨ Updated app.py

from flask import Flask
from backend.config import Config
from backend.routes import routes
from backend.extensions import db  # Import `db` from extensions
import backend.utils as utils

def create_app():
    app = Flask(__name__)
    app.config.from_object(Config)

    db.init_app(app)  # Initialize database
    app.register_blueprint(routes)

    with app.app_context():
        utils.reset_database()
        utils.initialize_db()

    return app  # βœ… Returns the app instance

if __name__ == '__main__':
    app = create_app()  # Create app dynamically
    app.run(debug=True)

✨ Updated backend/config.py

class Config:
    SQLALCHEMY_DATABASE_URI = 'sqlite:///example.db'
    SQLALCHEMY_TRACK_MODIFICATIONS = False

βœ… Now, config.py no longer depends on app.py, breaking the import loop.


πŸ“Œ Solution 3: Separate Flask Extensions into a New File

Another clean way to structure your app is to move db = SQLAlchemy() to a separate file.

✨ New backend/extensions.py

from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()  # Define the database instance separately

✨ Updated app.py

from flask import Flask
from backend.config import Config
from backend.routes import routes
from backend.extensions import db  # Import `db` separately
import backend.utils as utils

def create_app():
    app = Flask(__name__)
    app.config.from_object(Config)

    db.init_app(app)
    app.register_blueprint(routes)

    with app.app_context():
        utils.reset_database()
        utils.initialize_db()

    return app

if __name__ == '__main__':
    app = create_app()
    app.run(debug=True)

βœ… This keeps the database setup clean and prevents circular imports.


πŸš€ Bonus: Full Modern Flask App Structure

Here's a modern way to structure your Flask project:

/household-service-v2
│── app.py  # App Factory
│── backend/
β”‚   β”œβ”€β”€ __init__.py  # Initialize the Flask app
β”‚   β”œβ”€β”€ config.py  # App configuration
β”‚   β”œβ”€β”€ extensions.py  # Database and extensions
β”‚   β”œβ”€β”€ routes.py  # API routes
β”‚   β”œβ”€β”€ utils.py  # Helper functions
│── venv/

πŸ’‘ Why is this better?

  • πŸ”₯ Scalable – Easy to add new features without breaking imports.

  • βœ… No Circular Imports – Each component is modular.

  • πŸ› οΈ Best Practices – Follow Flask's recommended App Factory approach.


🎯 Conclusion

Circular imports in Flask happen when files depend on each other in a loop.
βœ… How to Fix It:

  1. Move imports inside functions

  2. Use the Flask App Factory Pattern (πŸ”₯ Best Solution)

  3. Separate Flask extensions into a new file (extensions.py)

By following these best practices, you’ll build modular, scalable, and bug-free Flask applications! πŸš€πŸ’‘


πŸ’¬ Got Questions?

Leave a comment below or share your thoughts! Happy coding! πŸŽ‰πŸ”₯

Setting Up a Virtual Environment and Installing Dependencies in Python

Setting Up a Virtual Environment and Installing Dependencies in Python

When working on a Python project, it's best practice to use a virtual environment to manage dependencies. This helps avoid conflicts between packages required by different projects. In this guide, we'll go through the steps to set up a virtual environment, create a requirements.txt file, install dependencies, upgrade packages, update dependencies, and activate the environment.

Step 1: Create a Virtual Environment

To create a virtual environment, run the following command in your terminal:

python -m venv venv

This will create a new folder named venv in your project directory, which contains the isolated Python environment.

Step 2: Activate the Virtual Environment

On macOS and Linux:

source venv/bin/activate

On Windows (Command Prompt):

venv\Scripts\activate

On Windows (PowerShell):

venv\Scripts\Activate.ps1

On Windows Subsystem for Linux (WSL) and Ubuntu:

source venv/bin/activate

Once activated, your terminal prompt will show (venv), indicating that the virtual environment is active.

Step 3: Create a requirements.txt File

A requirements.txt file lists all the dependencies your project needs. To create one, you can manually add package names or generate it from an existing environment:

pip freeze > requirements.txt

This will save a list of installed packages and their versions to requirements.txt.

Step 4: Install Dependencies

To install the dependencies listed in requirements.txt, use the following command:

pip install -r requirements.txt

This ensures all required packages are installed in the virtual environment.

Step 5: Upgrade Installed Packages

To upgrade all installed packages in the virtual environment, use:

pip install --upgrade pip setuptools wheel
pip list --outdated | awk '{print $1}' | xargs pip install --upgrade

This upgrades pip, setuptools, and wheel, followed by upgrading all outdated packages.

Step 6: Update Dependencies

To update dependencies to their latest versions, run:

pip install --upgrade -r requirements.txt

After updating, regenerate the requirements.txt file with:

pip freeze > requirements.txt

This ensures that your project stays up to date with the latest compatible package versions.

Conclusion

Using a virtual environment keeps your project dependencies organized and prevents conflicts. By following these steps, you can efficiently manage Python packages, keep them updated, and maintain a clean development setup.

Happy coding!

March 27, 2025

SQLite vs. Flask-SQLAlchemy: Understanding the Difference & Best Practices

Introduction

When developing a web application with Flask, one of the key decisions involves choosing and managing a database. SQLite and Flask-SQLAlchemy are two important components that serve different roles in this process. In this blog, we will explore their differences, use cases, and best practices for implementation.


Understanding SQLite

What is SQLite?

SQLite is a lightweight, self-contained relational database management system (RDBMS) that does not require a separate server process. It is widely used in mobile apps, small-scale applications, and as an embedded database.

Features of SQLite:

  • Serverless: No separate database server required.

  • Lightweight: Small footprint (~500 KB library size).

  • File-based: Stores the entire database in a single file.

  • ACID-compliant: Ensures data integrity through atomic transactions.

  • Cross-platform: Works on Windows, Mac, and Linux.

  • Easy to use: Requires minimal setup.

When to Use SQLite:

  • For small to medium-sized applications.

  • When you need a simple, portable database.

  • For local development and prototyping.

  • When database speed is a higher priority than scalability.


Understanding Flask-SQLAlchemy

What is Flask-SQLAlchemy?

Flask-SQLAlchemy is an Object Relational Mapper (ORM) for Flask that provides a high-level abstraction for working with databases using Python classes instead of raw SQL.

Features of Flask-SQLAlchemy:

  • Simplifies database interactions using Python objects.

  • Works with multiple databases (SQLite, PostgreSQL, MySQL, etc.).

  • Provides a session management system for queries.

  • Enables database migrations with Flask-Migrate.

  • Supports relationships and complex queries easily.

When to Use Flask-SQLAlchemy:

  • When working with Flask applications that need a database.

  • If you want an ORM to simplify queries and model relationships.

  • When you need to switch between different database backends.

  • To avoid writing raw SQL queries.


Key Differences Between SQLite and Flask-SQLAlchemy

Feature SQLite Flask-SQLAlchemy
Type Database Engine ORM (Object Relational Mapper)
Purpose Stores data as structured tables Provides a Pythonic way to interact with the database
Server Requirement Serverless (file-based) Can connect to multiple databases
Scalability Suitable for small applications Can work with larger databases like PostgreSQL & MySQL
Querying Uses SQL directly Uses Python objects & methods
Migration Support No built-in migration tool Works with Flask-Migrate for version control

Can You Use Both SQLite and Flask-SQLAlchemy?

Yes! In fact, Flask-SQLAlchemy can be used with SQLite to make database interactions easier.

How They Work Together:

  • SQLite acts as the actual database engine that stores the data.

  • Flask-SQLAlchemy provides an ORM (Object Relational Mapper) that allows you to interact with SQLite using Python objects instead of raw SQL queries.

Example Use Case:

You can configure Flask-SQLAlchemy to use SQLite as the database backend:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db'  # SQLite database
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(100), nullable=False)

# Create tables
with app.app_context():
    db.create_all()

Why Use Both?

  • Flask-SQLAlchemy simplifies database interactions while still using SQLite as the underlying database.

  • You can easily switch from SQLite to PostgreSQL or MySQL by changing the database URI.

  • Database migrations become easier with Flask-Migrate.


Which One Should You Use?

  • Use SQLite if:

    • You are building a small-scale application or prototype.

    • You need a lightweight, serverless database.

    • You want a simple, file-based database with minimal setup.

    • Your application does not require high concurrency or scalability.

  • Use Flask-SQLAlchemy if:

    • You are working on a Flask application that needs ORM features.

    • You want to use a database other than SQLite (e.g., PostgreSQL, MySQL).

    • You need database migration support (e.g., with Flask-Migrate).

    • You prefer writing Python code instead of raw SQL queries.

πŸš€ Recommended Approach: Use SQLite for development and testing, then switch to Flask-SQLAlchemy with a production-ready database like PostgreSQL or MySQL when scaling up.


Best Practices for Using SQLite with Flask-SQLAlchemy

1. Define a Proper Database URI

Ensure that your Flask app is configured correctly to use SQLite:

app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///your_database.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

2. Use Flask-Migrate for Database Migrations

Instead of dropping and recreating tables manually, use Flask-Migrate:

pip install flask-migrate
flask db init
flask db migrate -m "Initial migration"
flask db upgrade

3. Use Relationships Wisely

Define relationships using Flask-SQLAlchemy’s relationship and backref methods:

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(150), nullable=False)
    posts = db.relationship('Post', backref='author', lazy=True)

class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(200), nullable=False)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)

4. Optimize Performance

  • Use index=True on frequently searched columns.

  • Use lazy='selectin' for optimized relationship loading.

  • Close database sessions properly to avoid memory leaks:

    from flask_sqlalchemy import SQLAlchemy
    db = SQLAlchemy()
    

5. Use SQLite for Development, PostgreSQL for Production

SQLite is great for local development, but for production, consider switching to PostgreSQL:

app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://user:password@localhost/yourdb'

Tools to Work with SQLite & Flask-SQLAlchemy

1. DB Browser for SQLite

2. Flask-Migrate

  • Manages database migrations seamlessly.

  • Install via: pip install flask-migrate

3. SQLAlchemy ORM Explorer

4. SQLite CLI

  • Built-in SQLite shell to execute queries.

  • Open SQLite CLI using:

    sqlite3 your_database.db
    

Conclusion

SQLite and Flask-SQLAlchemy serve different purposes but work together efficiently in Flask applications. By using best practices, optimizing performance, and leveraging the right tools, you can build robust and scalable Flask applications.

πŸš€ Ready to take your Flask database management to the next level? Start integrating Flask-SQLAlchemy today!

March 26, 2025

Optimizing Netty Server Configuration in Spring Boot WebFlux

 

Optimizing Netty Server Configuration in Spring Boot WebFlux

Introduction

When building reactive applications using Spring Boot WebFlux (which relies on Netty), you may encounter issues related to request handling, such as:

  • 431 Request Header Fields Too Large

  • Connection timeouts

  • Memory overhead due to high traffic

  • Incorrect handling of forwarded headers behind proxies

These issues arise due to Netty’s default settings, which impose limits on header size, request line length, connection timeouts, and resource management. This article explores how to fine-tune Netty’s configuration for improved performance, stability, and debugging.


1️⃣ Why Modify Netty Server Customization?

Netty is highly configurable but ships with conservative defaults to protect against potential abuse (e.g., DoS attacks). However, in production environments with:

  • Large JWTs & OAuth Tokens (Authorization headers grow in size)

  • Reverse proxies (APISIX, Nginx, AWS ALB, Cloudflare) adding multiple headers

  • Microservices with long request URLs (especially GraphQL queries)

  • Security policies requiring extensive HTTP headers

…you may need to modify Netty’s default settings.


2️⃣ Key Netty Customization Areas

Here’s what we’ll fine-tune:

βœ… Increase Header & Request Line Size Limits βœ… Optimize Connection Handling & Keep-Alive βœ… Enable Access Logs for Debugging βœ… Improve Forwarded Header Support (For Reverse Proxies) βœ… Tune Write & Read Timeout Settings βœ… Limit Concurrent Connections to Prevent Overload βœ… Optimize Buffer Allocation for High Performance

πŸ”§ Customizing Netty in Spring Boot WebFlux

Spring Boot does not expose properties for Netty’s HTTP settings. Instead, we use a NettyReactiveWebServerFactory customizer:

import io.netty.channel.ChannelOption;
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.annotation.Bean;
import reactor.netty.http.server.HttpServer;

@Bean
public WebServerFactoryCustomizer<NettyReactiveWebServerFactory> nettyServerCustomizer() {
    return factory -> factory.addServerCustomizers(httpServer -> {
        return httpServer
                .tcpConfiguration(tcpServer -> tcpServer
                        .option(ChannelOption.SO_KEEPALIVE, true) // Keep connections alive
                        .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 60000) // 60s timeout
                        .metrics(true) // Enable metrics
                        .selectorOption(ChannelOption.SO_REUSEADDR, true) // Allow address reuse
                        .selectorOption(ChannelOption.SO_RCVBUF, 1048576) // 1MB receive buffer
                        .selectorOption(ChannelOption.SO_SNDBUF, 1048576)) // 1MB send buffer
                .accessLog(true) // Enable access logs for debugging
                .forwarded(true) // Handle forwarded headers properly
                .httpRequestDecoder(httpRequestDecoderSpec -> httpRequestDecoderSpec
                        .maxInitialLineLength(65536)  // Increase max URL length
                        .maxHeaderSize(16384))      // Increase max allowed header size
                .idleTimeout(java.time.Duration.ofSeconds(120)) // Set idle timeout to 2 minutes
                .connectionIdleTimeout(java.time.Duration.ofSeconds(60)); // Connection timeout 1 min
    });
}

3️⃣ Deep Dive: Why These Settings Matter

πŸ”Ή Increasing Header & Request Line Limits

.httpRequestDecoder(httpRequestDecoderSpec -> httpRequestDecoderSpec
        .maxInitialLineLength(65536)  // 64 KB for request line
        .maxHeaderSize(16384));      // 16 KB for headers

Why?

  • Fixes 431 Request Header Fields Too Large errors

  • Supports long URLs (useful for REST APIs and GraphQL)

  • Handles large OAuth/JWT tokens

  • Prevents API failures caused by large headers from reverse proxies

πŸ”Ή Keep Connections Alive (For Better Performance)

.option(ChannelOption.SO_KEEPALIVE, true)

Why?

  • Reduces TCP handshake overhead for high-traffic apps

  • Ensures persistent HTTP connections

πŸ”Ή Increase Connection Timeout

.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 60000)

Why?

  • Prevents premature timeouts during slow network conditions

  • Helps when interacting with slow backends (DBs, external APIs, etc.)

πŸ”Ή Enable Access Logs for Debugging

.accessLog(true)

Why?

  • Logs every HTTP request for easier debugging

  • Helps identify malformed headers causing failures

πŸ”Ή Improve Reverse Proxy Support

.forwarded(true)

Why?

  • Ensures correct handling of X-Forwarded-For, X-Real-IP, and Forwarded headers

  • Important for apps running behind APISIX, AWS ALB, or Nginx

πŸ”Ή Optimize Buffer Sizes

.selectorOption(ChannelOption.SO_RCVBUF, 1048576) // 1MB receive buffer
.selectorOption(ChannelOption.SO_SNDBUF, 1048576) // 1MB send buffer

Why?

  • Helps in high throughput scenarios

  • Reduces latency in data transmission

πŸ”Ή Limit Idle & Connection Timeouts

.idleTimeout(java.time.Duration.ofSeconds(120))
.connectionIdleTimeout(java.time.Duration.ofSeconds(60))

Why?

  • Prevents stale connections from consuming resources

  • Ensures efficient connection reuse


Final Thoughts

Fine-tuning Netty’s HTTP request handling can drastically improve Spring Boot WebFlux applications.

βœ… Increase header & request line limits βœ… Optimize connection handling βœ… Enable access logs & debugging tools βœ… Ensure compatibility with API gateways & proxies βœ… Optimize buffer sizes & memory management βœ… Limit idle connections for better resource management

By applying these configurations, you ensure better resilience, fewer errors, and optimized performance in high-traffic applications. πŸš€

Let me know if you need further refinements! 😊

March 23, 2025

Setting Up a Private GitHub Repository for a Flask-VueJS Project

Version control is essential for managing software projects efficiently. In this guide, we will walk through setting up a private GitHub repository, initializing a Flask-VueJS project, adding essential files, and defining an issue tracker for milestone tracking.


Step 1: Create a Private GitHub Repository

  1. Go to GitHub and log in.
  2. Click on the + (New) button in the top-right and select New repository.
  3. Enter a repository name (e.g., household-service-v2).
  4. Set visibility to Private.
  5. Click Create repository.

To clone it locally:

git clone https://github.com/YOUR_USERNAME/household-service-v2.git
cd household-service-v2

Step 2: Add a README.md File

A README file helps document your project. Create one:

echo "# Household Service v2\nA Flask-VueJS application for managing household services." > README.md

Commit and push:

git add README.md
git commit -m "Added README"
git push origin main

Step 3: Create a .gitignore File

The .gitignore file prevents unnecessary files from being tracked.

echo "# Python
__pycache__/
*.pyc
venv/
.env

# Node
node_modules/
dist/" > .gitignore

Commit the file:

git add .gitignore
git commit -m "Added .gitignore"
git push origin main

Step 4: Set Up Flask-VueJS Project Skeleton

Flask (Backend)

  1. Navigate to your project directory:
    mkdir backend && cd backend
    
  2. Create a virtual environment:
    python3 -m venv venv
    source venv/bin/activate  # For macOS/Linux
    # OR
    venv\Scripts\activate  # For Windows
    
  3. Install Flask:
    pip install flask
    
  4. Create an app.py file:
    from flask import Flask
    app = Flask(__name__)
    @app.route('/')
    def home():
        return 'Hello from Flask!'
    if __name__ == '__main__':
        app.run(debug=True)
    
  5. Run the Flask app:
    flask run
    

VueJS (Frontend)

  1. Navigate back to the root folder:
    cd ..
    
  2. Create a VueJS project:
    npx create-vue frontend
    cd frontend
    npm install
    npm run dev  # Start the Vue app
    

Commit the project structure:

git add backend frontend
git commit -m "Initialized Flask-VueJS project"
git push origin main

Step 5: Define an Issue Tracker for Milestone Progress

To track project milestones, create a Git tracker document:

touch git-tracker.md
echo "# Git Tracker for Household Service v2\n\n## Milestones:\n- [ ] Set up Flask backend\n- [ ] Initialize Vue frontend\n- [ ] Connect Flask API with Vue\n\n## Commits & Progress:\n- **$(date +%Y-%m-%d)** - Initialized Flask-Vue project (Commit SHA: XYZ)" > git-tracker.md

Commit and push:

git add git-tracker.md
git commit -m "Added Git tracker document"
git push origin main

Step 6: Add Collaborators (MADII-cs2006)

Using GitHub CLI

Ensure GitHub CLI is installed and authenticated:

gh auth login

Run the following command to add MADII-cs2006 as a collaborator:

gh api -X PUT "/repos/YOUR_USERNAME/household-service-v2/collaborators/MADII-cs2006"

Verify the collaborator list:

gh api "/repos/YOUR_USERNAME/household-service-v2/collaborators"

Using GitHub Web Interface

  1. Go to GitHub Repository β†’ Settings.
  2. Click Manage Access.
  3. Click Invite Collaborator.
  4. Enter MADII-cs2006 and send the invite.

Bonus: GitHub Adding Collaborator Video

To learn how to add a collaborator using VSCode and WSL, refer to this tutorial:


Conclusion

By following these steps, you now have a fully initialized Flask-VueJS project with:

  • A private GitHub repository
  • A README.md for project documentation
  • A .gitignore to prevent unnecessary files
  • A working Flask backend and Vue frontend
  • A Git tracker document for milestone tracking
  • A collaborator added for project contributions

This setup ensures smooth collaboration and effective version control. πŸš€ Happy coding! 🎯

Managing Multiple SSH Git Accounts on One Machine (For Nerds)

If you work with multiple Git accounts (e.g., personal, work, open-source contributions), managing SSH keys efficiently is crucial. This guide provides an in-depth look into setting up multiple SSH keys for different Git accounts, debugging common issues, and understanding SSH authentication at a deeper level.


1. Why You Need Multiple SSH Keys for Git

GitHub, GitLab, and Bitbucket allow SSH authentication, eliminating the need to enter credentials repeatedly. However, when you have multiple accounts, using the same SSH key across them may lead to conflicts.

For instance:

  • You might need different keys for personal and work repositories.
  • Some organizations enforce separate SSH keys for security.
  • You contribute to multiple projects and want isolated access.

Is This the Best Way? Are There Alternatives?

Using SSH keys is one of the most secure and convenient methods for authentication. However, there are other ways to manage multiple Git accounts:

  1. Using HTTPS & Git Credential Helper: Instead of SSH, you can authenticate using HTTPS and a credential helper to store your passwords securely.

    • Pros: No need to configure SSH.
    • Cons: Requires entering credentials periodically or using a credential manager.
  2. Using Different User Profiles: You can create separate user profiles on your machine and configure different Git settings for each.

    • Pros: Full isolation between accounts.
    • Cons: More cumbersome, requires switching users frequently.
  3. Using SSH Key Switching Manually: Instead of configuring ~/.ssh/config, you can manually specify the SSH key during each Git operation.

    • Example:
      GIT_SSH_COMMAND="ssh -i ~/.ssh/id_ed25519_work" git clone git@github.com:workuser/repo.git
      
    • Pros: No persistent configuration needed.
    • Cons: Requires specifying the key for every command.

Using ~/.ssh/config remains the most automated and hassle-free solution, making SSH authentication seamless across multiple accounts.


2. Generating Multiple SSH Keys

Each SSH key is a cryptographic pair consisting of a private and public key. To create separate keys for different accounts:

ssh-keygen -t ed25519 -C "your-email@example.com"

When prompted:

  • File to save the key: Choose a unique filename, e.g., ~/.ssh/id_ed25519_work for a work account and ~/.ssh/id_ed25519_personal for a personal account.
  • Passphrase: You can add one for extra security.

Example:

Generating public/private ed25519 key pair.
Enter file in which to save the key (/Users/yourname/.ssh/id_ed25519): ~/.ssh/id_ed25519_work
Enter passphrase (empty for no passphrase):

3. Adding SSH Keys to SSH Agent

Ensure the SSH agent is running:

eval "$(ssh-agent -s)"

Then, add your newly generated SSH keys:

ssh-add ~/.ssh/id_ed25519_work
ssh-add ~/.ssh/id_ed25519_personal

To list currently added SSH keys:

ssh-add -l

If you see The agent has no identities, restart the SSH agent and re-add the keys.


4. Configuring SSH for Multiple Git Accounts

Modify or create the SSH configuration file:

nano ~/.ssh/config

Add the following entries:

# Personal GitHub Account
Host github-personal
  HostName github.com
  User git
  IdentityFile ~/.ssh/id_ed25519_personal

# Work GitHub Account
Host github-work
  HostName github.com
  User git
  IdentityFile ~/.ssh/id_ed25519_work
  • Host github-personal: This is a custom alias for GitHub personal use.
  • IdentityFile ~/.ssh/id_ed25519_personal: Specifies the SSH key to use.
  • HostName github.com: The real hostname of GitHub.

Now, Git will use the correct key automatically.


5. Adding SSH Keys to GitHub / GitLab

Each Git service requires adding your public key for authentication.

Get the Public Key

To display the public key:

cat ~/.ssh/id_ed25519_work.pub

Copy the key and add it to GitHub / GitLab / Bitbucket under:

  • GitHub β†’ Settings β†’ SSH and GPG keys
  • GitLab β†’ Profile β†’ SSH Keys
  • Bitbucket β†’ Personal Settings β†’ SSH Keys

6. Cloning Repositories Using Multiple Accounts

When cloning a repository, use the custom alias instead of github.com:

# For personal account:
git clone git@github-personal:yourusername/personal-repo.git

# For work account:
git clone git@github-work:yourworkuser/work-repo.git

7. Testing SSH Connections

Verify that SSH authentication is working:

ssh -T git@github-personal
ssh -T git@github-work

Expected output:

Hi yourusername! You've successfully authenticated...

If you see a permission error, ensure the correct key is added to the SSH agent (ssh-add -l).


8. Fixing Common Issues

1. SSH Key Not Used Correctly

Run:

ssh -vT git@github-personal

If you see Permission denied (publickey), make sure:

  • The correct SSH key is added to the SSH agent.
  • The key is correctly configured in ~/.ssh/config.

2. Wrong Host in Git Remote URL

Check the remote URL:

git remote -v

If it shows github.com, update it:

git remote set-url origin git@github-work:yourworkuser/work-repo.git

3. Too Many Authentication Failures

If you have multiple SSH keys and face authentication failures, specify the identity explicitly:

ssh -i ~/.ssh/id_ed25519_work -T git@github.com

9. Advanced: Using Different Git Configurations Per Account

If you want different Git usernames and emails for each account:

git config --global user.name "Personal Name"
git config --global user.email "personal@example.com"

For work repos:

git config --local user.name "Work Name"
git config --local user.email "work@example.com"

This ensures commits from work and personal accounts are correctly attributed.


Final Thoughts

By configuring multiple SSH keys, you can seamlessly work with different Git accounts without switching credentials manually. Understanding SSH authentication helps prevent conflicts and ensures a smooth development workflow.

Happy coding! πŸš€