GEMVC - The PHP Multi-Platform Microservices REST API Framework
composer require gemvc/library
php vendor/bin/gemvc initStop using a Swiss Army Knife when you need a Scalpel. GEMVC is a PHP Multi-Platform(OpenSwoole, Apache and NginX) specialized, ultra-lightweight framework designed for high-performance REST APIs and Microservices.
GEMVC is not a replacement for lovely Laravel or powerful Symfony; it is a complementary tool designed to solve special challenges. It is always good to have it in your arsenal!
In GEMVC, architecture is just a recommendation. GEMVC NEVER forces you to follow its rules. You can implement the recommended 4-layer architecture, or write your own familiar 3, 2, or even single-layer code!
Thanks to its transparent structure, strict naming conventions, and clean 4-layer separation of concerns (with no magic functions or hidden classes), GEMVC is natively AI-Friendly. AI Assistants can easily understand your code context and assist you efficiently. Writing code with GEMVC is a joy for both humans and AI. GEMVC is designed to work seamlessly with Cursor, Copilot, and ChatGPT. We include pre-configured context files (
AI_API_REFERENCE.md,.cursorrules) right in the root. Your AI assistant already knows how to write GEMVC code perfectly!
It takes only a couple of hours to master this tool. GEMVC respects your time and is not here to replace your existing knowledge, but to sharpen it.
In GEMVC, Sanitization IS Documentation. When you sanitize API inputs (as you normally should), the framework automatically generates full documentation for your endpoints. You get beautiful, well-designed documentation simply by defining your validation rules. Plus, you get out-of-the-box support to export your latest API version to Postman with a single click. Your frontend developers will love GEMVC!
Write standard, synchronous PHP code (like you always do) on Apache/Nginx. Switch to OpenSwoole, and your exact same code automatically runs as Asynchronous Non-blocking I/O. Zero code changes required.
You can easily define custom Class Templates for any layer, and then CLI Commands will generate your classes in seconds!
You can use GEMVC as a library within a legacy project to migrate to a modern architecture step-by-step, without breaking existing functionality. Or, for new projects, simply install it via Composer and run the
gemvc initcommand to use it as a modern framework from day one.
When coding with GEMVC, you can enjoy PHPStan Level 9 compliance! The framework natively supports strong typingβwhile not forced, it is strongly recommended. It also comes with PHPUnit and Pest support right out of the box during the installation process. You can easily test your product using your beloved PHPUnit or the beautiful Pest framework. Plus, the 4-layer architecture makes creating comprehensive Unit and Integration tests a breeze.
It is Open Source, MIT Licensed, and small. There is no hidden code or "black box". You can download the source code, analyze it, test it, and use it with peace of mind. It is built with love to make your life as a developer more enjoyable.
ATTENTION: If you are an AI assistant (Cursor AI, GitHub Copilot, Claude, etc.), you MUST read the following file first:
GEMVC is NOT Laravel or Symfony - It has its own unique architecture, naming conventions, and design philosophy. This guide will teach you how to write code the GEMVC way.
β οΈ QUICK_START_AI.md - MASTER INSTRUCTIONS FOR AI ASSISTANTS
This file will:
- β Instruct you to read ALL GEMVC documentation files
- β Teach you the complete 4-layer architecture (MANDATORY)
- β Show you correct method signatures and return types
- β Explain security features (90% automatic)
- β Provide code generation patterns you MUST follow
- β List common mistakes you MUST avoid
After reading QUICK_START_AI.md, you should read:
.cursorrules- Complete framework rulesAI_API_REFERENCE.md- All method signaturesAI_CONTEXT.md- Quick examplesGEMVC_GUIDE.md- Code generation patterns
GEMVC is a multi-platform PHP REST API framework that works identically on:
- β OpenSwoole (async, WebSocket, hot reload)
- β Apache (traditional PHP-FPM)
- β Nginx (high performance, reverse proxy)
Key Features:
- π 90% Security Automatic - Input sanitization, SQL injection prevention, path protection
- π Webserver-Agnostic - Same code works on all platforms
- π οΈ CLI Code Generation - Generate Services, Controllers, Models, Tables
- π Simple API - Clean, straightforward code structure
- β‘ High Performance - Connection pooling, async capabilities
- β PHPStan Level 9 - Write type-safe, bug-free code with the highest static analysis level
GEMVC uses a 4-layer architecture pattern:
API Layer (app/api/) β URL endpoints, schema validation
β
Controller Layer (app/controller/) β Business logic orchestration
β
Model Layer (app/model/) β Data logic, validations
β
Table Layer (app/table/) β Database access, queries
Example Flow:
POST /api/User/create
β
app/api/User.php::create() β Validates schema
β
app/controller/UserController.php β Handles business logic
β
app/model/UserModel.php β Data validations, transformations
β
app/table/UserTable.php β Database operations
Purpose: URL endpoints, request validation, authentication
Naming: PascalCase (e.g., User.php, Product.php)
Responsibilities:
- Define request schemas (
definePostSchema(),defineGetSchema()) - Handle authentication (
$request->auth()) - Delegate to Controller layer
Example (app/api/User.php):
<?php
namespace App\Api;
use App\Controller\UserController;
use Gemvc\Core\ApiService;
use Gemvc\Http\Request;
use Gemvc\Http\JsonResponse;
class User extends ApiService
{
public function __construct(Request $request)
{
parent::__construct($request);
}
public function create(): JsonResponse
{
// Validate request schema
if(!$this->request->definePostSchema([
'name' => 'string',
'email' => 'email',
'password' => 'string'
])) {
return $this->request->returnResponse();
}
// Delegate to controller
return (new UserController($this->request))->create();
}
}Key Points:
- β
Extends
ApiService(orSwooleApiServicefor OpenSwoole) - β
Uses
definePostSchema()for validation - β
Uses
?prefix for optional fields:'?name' => 'string' - β Delegates to Controller, doesn't handle business logic
Purpose: Business logic orchestration
Naming: PascalCase + "Controller" suffix (e.g., UserController.php)
Responsibilities:
- Orchestrate business logic
- Map request data to models
- Handle request/response flow
Example (app/controller/UserController.php):
<?php
namespace App\Controller;
use App\Model\UserModel;
use Gemvc\Core\Controller;
use Gemvc\Http\Request;
use Gemvc\Http\JsonResponse;
class UserController extends Controller
{
public function __construct(Request $request)
{
parent::__construct($request);
}
public function create(): JsonResponse
{
// Map POST data to Model with custom handlers
$model = $this->request->mapPostToObject(
new UserModel(),
[
'email' => 'email',
'name' => 'name',
'description' => 'description',
'password' => 'setPassword()' // Calls setPassword() method
]
);
if(!$model instanceof UserModel) {
return $this->request->returnResponse();
}
return $model->createModel();
}
}Key Points:
- β
Extends
Controller - β
Uses
mapPostToObject()to convert request to model - β
Can specify method calls:
'password' => 'setPassword()' - β Delegates to Model layer
Purpose: Data logic, validations, transformations
Naming: PascalCase + "Model" suffix (e.g., UserModel.php)
Responsibilities:
- Business validations (e.g., duplicate email check)
- Data transformations (e.g., password hashing)
- Error handling
Example (app/model/UserModel.php):
<?php
namespace App\Model;
use App\Table\UserTable;
use Gemvc\Helper\CryptHelper;
use Gemvc\Http\JsonResponse;
use Gemvc\Http\Response;
class UserModel extends UserTable
{
public function createModel(): JsonResponse
{
// Business validation: Check duplicate email
$this->email = strtolower($this->email);
$found = $this->selectByEmail($this->email);
if ($found) {
return Response::unprocessableEntity("User already exists");
}
// Data transformation: Hash password
$this->setPassword($this->password);
// Perform database operation
$success = $this->insertSingleQuery();
if ($this->getError()) {
return Response::internalError($this->getError());
}
return Response::created($this, 1, "User created successfully");
}
public function setPassword(string $plainPassword): void
{
$this->password = CryptHelper::hashPassword($plainPassword);
}
}Key Points:
- β
Extends corresponding
Tableclass (e.g.,UserModel extends UserTable) - β Contains business logic and validations
- β
Uses
insertSingleQuery(),updateSingleQuery(),deleteByIdQuery() - β
Returns
JsonResponseobjects
Purpose: Database access, queries, schema definition
Naming: PascalCase + "Table" suffix (e.g., UserTable.php)
Responsibilities:
- Define database table structure
- Define properties matching database columns
- Provide query methods
Example (app/table/UserTable.php):
<?php
namespace App\Table;
use Gemvc\Database\Table;
use Gemvc\Database\Schema;
class UserTable extends Table
{
// Properties match database columns
public int $id;
public string $name;
public string $email;
public ?string $description;
protected string $password; // Protected = not exposed in selects
protected array $_type_map = [
'id' => 'int',
'name' => 'string',
'email' => 'string',
'description' => 'string',
'password' => 'string',
];
public function __construct()
{
parent::__construct();
}
public function getTable(): string
{
return 'users'; // Database table name
}
public function defineSchema(): array
{
return [
Schema::index('email'),
Schema::unique('email'),
Schema::index('description')
];
}
// Custom query methods
public function selectById(int $id): null|static
{
$result = $this->select()->where('id', $id)->limit(1)->run();
return $result[0] ?? null;
}
public function selectByEmail(string $email): null|static
{
$arr = $this->select()->where('email', $email)->limit(1)->run();
return $arr[0] ?? null;
}
}Key Points:
- β
Extends
Tableclass - β Properties match database columns (with types)
- β
protectedproperties are not exposed in SELECT queries - β
Properties starting with
_are ignored in CRUD operations (see below) - β
Uses fluent query builder:
$this->select()->where()->limit()->run() - β
Returns
null|staticornull|static[]for query methods
Important Feature: Properties starting with _ are completely ignored in all CRUD table operations!
This allows you to:
- β Aggregate other models (composition)
- β Store arrays of related models
- β Create relationships without affecting database operations
- β Use PHPStan Level 9 type checking for aggregated models
Properties starting with _ are skipped:
- β Not included in
INSERToperations - β Not included in
UPDATEoperations - β Not included in table schema generation
- β Can be used for aggregation/composition
- β Can be public, private, or protected
User Model with Profile Aggregation:
<?php
namespace App\Model;
use App\Table\UserTable;
use App\Model\Profile; // Aggregated model
class UserModel extends UserTable
{
// This property is IGNORED in database operations
public ?Profile $_profile = null;
/**
* Get user with profile loaded
*/
public function withProfile(): self
{
if ($this->_profile === null && $this->id) {
$profileTable = new ProfileTable();
$this->_profile = $profileTable->selectByUserId($this->id);
}
return $this;
}
/**
* Set profile
*/
public function setProfile(Profile $profile): void
{
$this->_profile = $profile;
}
}Usage:
$user = new UserModel();
$user->id = 1;
$user->name = "John";
$user->email = "john@example.com";
// Add profile without affecting database operations
$profile = new Profile();
$profile->bio = "Software Developer";
$user->_profile = $profile;
// Save user (profile is NOT inserted, only user data)
$user->insertSingleQuery(); // Only inserts: id, name, emailUser Model with Orders Array (PHPStan Level 9):
<?php
namespace App\Model;
use App\Table\UserTable;
use App\Model\Order;
class UserModel extends UserTable
{
/**
* Array of Order models - ignored in CRUD operations
* @var array<Order>
*/
public array $_orders = [];
/**
* Get user's orders
* @return array<Order>
*/
public function orders(): array
{
if (empty($this->_orders) && $this->id) {
$orderTable = new OrderTable();
$this->_orders = $orderTable->selectByUserId($this->id);
}
return $this->_orders;
}
/**
* Add order to user
*/
public function addOrder(Order $order): void
{
$this->_orders[] = $order;
}
/**
* Create order for user
*/
public function createOrder(array $orderData): Order
{
$order = new Order();
$order->user_id = $this->id;
$order->amount = $orderData['amount'];
$order->insertSingleQuery();
$this->_orders[] = $order;
return $order;
}
}Usage:
$user = new UserModel();
$user->id = 1;
$user->name = "John";
// Add orders (ignored in database operations)
$order1 = new Order();
$order1->amount = 100;
$user->addOrder($order1);
$order2 = new Order();
$order2->amount = 200;
$user->addOrder($order2);
// Save user (orders array is NOT inserted!)
$user->insertSingleQuery(); // Only inserts: id, name
// Later, create actual orders in database
foreach ($user->_orders as $order) {
$order->user_id = $user->id;
$order->insertSingleQuery(); // Save each order separately
}Product Model with Categories and Reviews:
<?php
namespace App\Model;
use App\Table\ProductTable;
use App\Model\Category;
use App\Model\Review;
class ProductModel extends ProductTable
{
/**
* Single related model
* @var Category|null
*/
public ?Category $_category = null;
/**
* Array of related models
* @var array<Review>
*/
public array $_reviews = [];
/**
* Load product with category and reviews
*/
public function loadRelations(): self
{
if ($this->id) {
// Load category
$categoryTable = new CategoryTable();
$this->_category = $categoryTable->selectById($this->category_id);
// Load reviews
$reviewTable = new ReviewTable();
$this->_reviews = $reviewTable->selectByProductId($this->id);
}
return $this;
}
/**
* Get average rating
*/
public function getAverageRating(): float
{
if (empty($this->_reviews)) {
return 0.0;
}
$total = 0;
foreach ($this->_reviews as $review) {
$total += $review->rating;
}
return round($total / count($this->_reviews), 2);
}
}Usage:
$product = new ProductModel();
$product->id = 1;
$product->name = "Laptop";
$product->price = 999.99;
// Load relations
$product->loadRelations();
// Use aggregated data
echo $product->_category->name; // "Electronics"
echo $product->getAverageRating(); // 4.5
// Save product (category and reviews are NOT affected!)
$product->updateSingleQuery(); // Only updates: id, name, price- Use Descriptive Names:
$_profile,$_orders,$_reviews - Add Type Hints: Use PHPStan Level 9 compatible types
public ?Profile $_profile = null; // Single model public array $_orders = []; // Array of models public array<int, Order> $_orders = []; // Typed array (PHPStan)
- Create Helper Methods:
orders(),withProfile(),loadRelations() - Lazy Loading: Load aggregated data only when needed
- Keep Separate: Don't mix database columns with aggregated properties
// Pattern 1: Single aggregation
public ?Profile $_profile = null;
// Pattern 2: Array aggregation
public array $_orders = [];
// Pattern 3: Private aggregation with getter
private array $_reviews = [];
public function getReviews(): array {
return $this->_reviews;
}
// Pattern 4: Protected aggregation
protected ?Category $_category = null;// β
Full type safety with PHPStan Level 9
/** @var array<Order> */
public array $_orders = [];
// β
Nullable single model
public ?Profile $_profile = null;
// β
Typed array with PHPDoc
/**
* @var array<int, Review>
*/
public array $_reviews = [];Result: Clean, type-safe code with powerful aggregation capabilities! π―
GEMVC maps URLs to code automatically:
URL: /api/User/create
β
Extracts: Service = "User", Method = "create"
β
Loads: app/api/User.php
β
Calls: User::create()
URL Structure:
/api/{ServiceName}/{MethodName}
Examples:
POST /api/User/createβUser::create()GET /api/User/read/?id=1βUser::read()POST /api/User/updateβUser::update()POST /api/User/deleteβUser::delete()GET /api/User/listβUser::list()
Configuration (.env):
SERVICE_IN_URL_SECTION=1 # Service name position in URL
METHOD_IN_URL_SECTION=2 # Method name position in URLLaravel/Symfony: MVC (Model-View-Controller)
GEMVC: 4-Layer (API β Controller β Model β Table)
Laravel: Routes defined in routes/web.php or routes/api.php
GEMVC: Automatic URL-to-class mapping (/api/User/create β User::create())
Laravel: Form Requests, Validation Rules
GEMVC: definePostSchema() method with inline validation
Laravel: Eloquent ORM (User::create(), User::find())
GEMVC: Fluent Query Builder ($this->select()->where()->run())
Laravel: Singular models (User), plural tables (users)
GEMVC: Consistent naming (User API, UserController, UserModel, UserTable)
Laravel: Various response types
GEMVC: Consistent JsonResponse with Response::success(), Response::created(), etc.
Laravel: Manual middleware, CSRF tokens
GEMVC: 90% automatic - Input sanitization, SQL injection prevention built-in
π¦ For complete installation guide, see INSTALLATION.md
1. Install GEMVC:
composer require gemvc/swoole2. Initialize Project:
php vendor/bin/gemvc init
# Select: 1) OpenSwoole, 2) Apache, or 3) Nginx
# Install PHPStan: Yes (recommended)
# Setup Docker: Yes (recommended)3. Start Server:
# With Docker
docker-compose up -d
# Without Docker (OpenSwoole)
php index.php4. Test Server:
# Visit: http://localhost:9501/api
# Should return: "GEMVC server is running"5. Setup Database (optional):
php vendor/bin/gemvc db:init
php vendor/bin/gemvc db:migrate UserTable6. Generate Your Service:
php vendor/bin/gemvc create:crud Product7. Run PHPStan:
vendor/bin/phpstan analyseβ For detailed step-by-step instructions, see INSTALLATION.md
Request:
POST /api/User/create
Content-Type: application/json
{
"name": "John Doe",
"email": "john@example.com",
"password": "secret123"
}Flow:
User::create()validates schemaUserController::create()maps data toUserModelUserModel::createModel()validates business rules, hashes passwordUserTable::insertSingleQuery()inserts into database
Response:
{
"response_code": 201,
"message": "created",
"count": 1,
"service_message": "User created successfully",
"data": {
"id": 1,
"name": "John Doe",
"email": "john@example.com",
"description": null
}
}Request:
GET /api/User/read/?id=1Flow:
User::read()validates GET parameterUserController::read()delegates to modelUserModel::readModel()callsselectById()- Password is hidden (
password = "-")
Response:
{
"response_code": 200,
"message": "OK",
"count": 1,
"service_message": "User retrieved successfully",
"data": {
"id": 1,
"name": "John Doe",
"email": "john@example.com",
"password": "-"
}
}Request:
GET /api/User/list/?sort_by=name&find_like=name=JohnFlow:
User::list()definesfindable()andsortable()fieldsUserController::list()usescreateList()helper- Automatic filtering and sorting applied
Response:
{
"response_code": 200,
"message": "OK",
"count": 1,
"service_message": "Users retrieved successfully",
"data": [
{
"id": 1,
"name": "John Doe",
"email": "john@example.com"
}
]
}-
Study the User Example (
src/startup/user/)- See how all 4 layers work together
- Understand request flow
- Learn validation patterns
-
Generate Your First CRUD
gemvc create:crud Product
-
Run PHPStan Level 9
vendor/bin/phpstan analyse
- Fix any type errors
- Write type-safe code
- Catch bugs early!
-
Customize Templates (
templates/cli/)- Edit templates to match your coding style
- Add custom methods and patterns
-
Read Documentation
ARCHITECTURE.md- Full architecture detailsSECURITY.md- Security featuresTEMPLATE_SYSTEM.md- Template customization
- GEMVC is NOT Laravel - Don't expect Laravel conventions
- GEMVC is NOT Symfony - Different architecture and patterns
- Follow GEMVC patterns - Use the User example as a reference
- 4-Layer Architecture - API β Controller β Model β Table
- Automatic Security - Input sanitization, SQL injection prevention built-in
- β
Extend
ApiServicefor API classes - β
Extend
Controllerfor controllers - β
Extend
Tablefor models and tables - β
Use
definePostSchema()for validation - β Use fluent query builder for database operations
- β
Return
JsonResponseobjects - β Use PHPStan Level 9 - Write type-safe code!
- β Add type hints to all methods and properties
- β
Use strict types:
declare(strict_types=1); - β
Use
_prefix for aggregation - Properties starting with_are ignored in CRUD operations
- β Don't use Laravel conventions
- β Don't create routes files
- β Don't use Eloquent-style syntax
- β Don't skip the 4-layer architecture
- β Don't manually sanitize inputs (it's automatic!)
- β Don't skip PHPStan - Run it regularly!
- β Don't ignore type errors - Fix them!
- β Don't use
mixedtypes without reason
GEMVC has extensive documentation to help you understand every aspect of the framework. All documentation files (.md) are important and provide different perspectives on GEMVC:
ποΈ ARCHITECTURE.md - Complete Architecture Overview
Learn: Overall framework structure, component breakdown, design patterns, request flow
- Directory structure and component organization
- Webserver-agnostic architecture
- Automatic security features
- Request flow for Apache and OpenSwoole
- Performance optimizations
- Design patterns used
π οΈ CLI.md - CLI Commands Reference
Learn: All command-line tools, code generation, project management
- Complete CLI command reference
- Command architecture and class hierarchy
- How commands extend
Commandbase class - How
AbstractInituses Template Method pattern - How
DockerComposeInitmanages Docker services - How
ProjectHelperresolves paths - Step-by-step examples and troubleshooting
ποΈ DATABASE_LAYER.md - Database Layer Guide
Learn: Table layer, schema definition, type mapping, migrations
- How all table classes must extend
Table - Understanding
$_type_mapproperty - Using
defineSchema()for constraints - Property mapping and visibility rules
- Schema constraints (primary, unique, foreign keys, indexes)
- Complete examples and best practices
π HTTP_REQUEST_LIFE_CYCLE.md - Request Handling
Learn: Server-agnostic HTTP request handling, adapters, unified Request object
- How server adapters (
ApacheRequest,SwooleRequest) work - Unified
Requestobject design - Request life cycle for Apache and OpenSwoole
- Automatic input sanitization
- Response handling abstraction
- Security features in request processing
π¨ TEMPLATE_SYSTEM.md - Customizable Templates
Learn: Code generation templates, customization, template variables
- How templates are copied during
gemvc init - Template lookup priority (project vs vendor)
- Available templates (service, controller, model, table)
- Template variables and replacement
- Customization examples
- Best practices for template management
π SECURITY.md - Security Features
Learn: Security architecture, attack prevention, automatic security
- Multi-layer security architecture
- 90% automatic security coverage
- Input sanitization (XSS prevention)
- SQL injection prevention
- Header sanitization
- File security (MIME, signature, encryption)
- JWT authentication/authorization
- Schema validation for mass assignment prevention
- CHANGELOG.md - Version history and notable changes
- SECURITY.md - Detailed security features and best practices
- LICENSE - License information
- Example Code:
src/startup/user/- Complete User implementation (API, Controller, Model, Table) - Template Examples:
templates/cli/- Default code generation templates - Stubs:
src/stubs/- IDE type stubs for OpenSwoole and Redis
To understand GEMVC architecture:
- Start with README.MD (this file) for overview
- Read ARCHITECTURE.md for deep dive
- Study
src/startup/user/for code examples
To understand CLI commands:
- Read CLI.md for complete command reference
- Check how commands extend
Commandbase class - Understand Template Method pattern in
AbstractInit
To understand database layer:
- Read DATABASE_LAYER.md for Table layer guide
- Learn about
$_type_mapanddefineSchema() - Understand how all table classes extend
Table
To understand HTTP request handling:
- Read HTTP_REQUEST_LIFE_CYCLE.md
- Learn about server adapters (
ApacheRequest,SwooleRequest) - Understand unified
Requestobject
To understand template system:
- Read TEMPLATE_SYSTEM.md
- Learn template customization
- Understand template variable replacement
To understand security:
- Read SECURITY.md
- Learn about automatic security features
- Understand attack prevention mechanisms
- π Read ARCHITECTURE.md for detailed architecture
- π Read SECURITY.md for security features
- π¨ Read TEMPLATE_SYSTEM.md for template customization
- π‘ Study
src/startup/user/for code examples
GEMVC is built with PHPStan Level 9 (the highest level!) and you should use it too!
PHPStan Level 9 catches:
- β Type errors before runtime
- β Null pointer exceptions
- β Undefined method calls
- β Incorrect array access
- β Type mismatches
- β Missing return types
- β And much more!
During gemvc init, you'll be asked if you want to install PHPStan. Say YES!
Or install manually:
composer require --dev phpstan/phpstan# Run analysis
vendor/bin/phpstan analyse
# Or use composer script
composer phpstanWithout PHPStan (bugs in production):
public function getUser($id)
{
return $this->selectById($id)->name; // β Might be null!
}With PHPStan Level 9 (caught at development):
public function getUser(int $id): ?UserModel
{
$user = $this->selectById($id);
if (!$user) {
return null;
}
return $user; // β
Type-safe!
}- Type Safety: Catch errors before they happen
- Better IDE Support: Auto-completion, refactoring
- Cleaner Code: Forces you to write explicit types
- Fewer Bugs: Static analysis catches issues early
- Team Consistency: Everyone writes code the same way
GEMVC includes:
- β Level 9 configuration (highest level)
- β OpenSwoole stubs for proper type checking
- β Redis stubs for connection type safety
- β
Pre-configured
phpstan.neonfile
Use PHPStan Level 9 - Write clean, type-safe, bug-free code! π―
- π€ FOR AI ASSISTANTS - READ THIS FIRST!
- π¦ Installation Guide β Start Here!
- What is GEMVC?
- Architecture
MIT License bei Ali Khorsandfard gemvc.de(https://www.gemvc.de)
Built with β€οΈ for developers who want simplicity, security, and performance.
