Skip to main content
The create:crud command automatically generates a fully functional CRUD (Create, Read, Update, Delete) controller for an existing entity.

Usage

framefox create:crud [entity_name]

Arguments

entity_name
string
The name of the entity in snake_case. The entity and its repository must already exist.

Prerequisites

Before creating a CRUD controller:
  1. Entity must exist - Create with framefox create:entity
  2. Repository must exist - Auto-created with entity
  3. Database must be set up - Run migrations

CRUD Types

Choose between two types:

1. API CRUD Controller

JSON-based REST API with full CRUD operations. Features:
  • List all records (GET)
  • Get single record (GET)
  • Create record (POST)
  • Update record (PUT)
  • Delete record (DELETE)
  • JSON responses
  • Automatic serialization

2. Templated CRUD Controller

Server-side rendered HTML pages with forms. Features:
  • List view with table
  • Detail view
  • Create form
  • Edit form
  • Delete confirmation
  • Form validation
  • Auto-generated templates

Example Session

$ framefox create:crud

What is the name of the entity you want to create a CRUD with ?(snake_case)
Entity name: user

What type of controller do you want to create?

1. API CRUD controller
2. Templated CRUD controller

CRUD controller type: 1

 CRUD Controller created successfully: src/controller/user_controller.py

API CRUD Controller

Generated Controller

Creates src/controller/user_controller.py:
from framefox.core.di.decorator.service import service
from framefox.core.http.request import Request
from framefox.core.http.response import Response
from framefox.core.routing.decorator.route import route
from src.entity.user import User
from src.repository.user_repository import UserRepository


@service
class UserController:
    def __init__(self, user_repository: UserRepository):
        self.user_repository = user_repository

    @route("/api/user", methods=["GET"])
    def index(self) -> Response:
        """List all users"""
        users = self.user_repository.find_all()
        return Response.json([
            user.to_dict() for user in users
        ])

    @route("/api/user/<int:id>", methods=["GET"])
    def show(self, id: int) -> Response:
        """Get a specific user"""
        user = self.user_repository.find(id)
        if not user:
            return Response.json(
                {"error": "User not found"},
                status=404
            )
        return Response.json(user.to_dict())

    @route("/api/user", methods=["POST"])
    def create(self, request: Request) -> Response:
        """Create a new user"""
        data = request.json()
        user = User(**data)
        self.user_repository.save(user)
        return Response.json(
            user.to_dict(),
            status=201
        )

    @route("/api/user/<int:id>", methods=["PUT"])
    def update(self, id: int, request: Request) -> Response:
        """Update a user"""
        user = self.user_repository.find(id)
        if not user:
            return Response.json(
                {"error": "User not found"},
                status=404
            )
        
        data = request.json()
        for key, value in data.items():
            setattr(user, key, value)
        
        self.user_repository.save(user)
        return Response.json(user.to_dict())

    @route("/api/user/<int:id>", methods=["DELETE"])
    def delete(self, id: int) -> Response:
        """Delete a user"""
        user = self.user_repository.find(id)
        if not user:
            return Response.json(
                {"error": "User not found"},
                status=404
            )
        
        self.user_repository.delete(user)
        return Response.json(
            {"message": "User deleted"},
            status=200
        )

API Endpoints

MethodEndpointDescription
GET/api/userList all users
GET/api/user/{id}Get user by ID
POST/api/userCreate new user
PUT/api/user/{id}Update user
DELETE/api/user/{id}Delete user

Testing API Endpoints

# List all users
curl http://localhost:8000/api/user

# Get user by ID
curl http://localhost:8000/api/user/1

# Create user
curl -X POST http://localhost:8000/api/user \
  -H "Content-Type: application/json" \
  -d '{"username":"john","email":"[email protected]"}'

# Update user
curl -X PUT http://localhost:8000/api/user/1 \
  -H "Content-Type: application/json" \
  -d '{"username":"john_updated"}'

# Delete user
curl -X DELETE http://localhost:8000/api/user/1

Templated CRUD Controller

Generated Files

Creates multiple files:
  1. Controller: src/controller/user_controller.py
  2. Form Type: src/form/user_type.py
  3. Templates: templates/user/
    • index.html - List view
    • create.html - Create form
    • read.html - Detail view
    • update.html - Edit form

Generated Controller

from framefox.core.di.decorator.service import service
from framefox.core.http.request import Request
from framefox.core.http.response import Response
from framefox.core.routing.decorator.route import route
from src.entity.user import User
from src.form.user_type import UserType
from src.repository.user_repository import UserRepository


@service
class UserController:
    def __init__(self, user_repository: UserRepository):
        self.user_repository = user_repository

    @route("/user", methods=["GET"])
    def index(self) -> Response:
        """List all users"""
        users = self.user_repository.find_all()
        return Response.render("user/index.html", {
            "users": users
        })

    @route("/user/<int:id>", methods=["GET"])
    def show(self, id: int) -> Response:
        """Show user details"""
        user = self.user_repository.find(id)
        if not user:
            return Response.redirect("/user")
        return Response.render("user/read.html", {
            "user": user
        })

    @route("/user/create", methods=["GET"])
    def create(self) -> Response:
        """Show create form"""
        form = UserType()
        return Response.render("user/create.html", {
            "form": form
        })

    @route("/user", methods=["POST"])
    def store(self, request: Request) -> Response:
        """Store new user"""
        form = UserType(request.form())
        if form.is_valid():
            user = User(**form.get_data())
            self.user_repository.save(user)
            return Response.redirect("/user")
        return Response.render("user/create.html", {
            "form": form
        })

    @route("/user/<int:id>/edit", methods=["GET"])
    def edit(self, id: int) -> Response:
        """Show edit form"""
        user = self.user_repository.find(id)
        if not user:
            return Response.redirect("/user")
        form = UserType(data=user.to_dict())
        return Response.render("user/update.html", {
            "user": user,
            "form": form
        })

    @route("/user/<int:id>", methods=["POST"])
    def update(self, id: int, request: Request) -> Response:
        """Update user"""
        user = self.user_repository.find(id)
        if not user:
            return Response.redirect("/user")
        
        form = UserType(request.form())
        if form.is_valid():
            for key, value in form.get_data().items():
                setattr(user, key, value)
            self.user_repository.save(user)
            return Response.redirect(f"/user/{id}")
        
        return Response.render("user/update.html", {
            "user": user,
            "form": form
        })

    @route("/user/<int:id>/delete", methods=["POST"])
    def delete(self, id: int) -> Response:
        """Delete user"""
        user = self.user_repository.find(id)
        if user:
            self.user_repository.delete(user)
        return Response.redirect("/user")

Generated Form Type

src/form/user_type.py:
from framefox.core.form.form_type import FormType
from framefox.core.form.field import StringField, EmailField, BooleanField
from framefox.core.form.validator import Required, Length, Email


class UserType(FormType):
    username = StringField(
        label="Username",
        validators=[Required(), Length(min=3, max=100)]
    )
    email = EmailField(
        label="Email",
        validators=[Required(), Email()]
    )
    is_active = BooleanField(
        label="Active",
        default=True
    )

Generated Templates

List view (templates/user/index.html):
{% extends "base.html" %}

{% block title %}Users{% endblock %}

{% block content %}
<div class="container">
    <h1>Users</h1>
    <a href="/user/create" class="btn btn-primary">Create New User</a>
    
    <table class="table">
        <thead>
            <tr>
                <th>ID</th>
                <th>Username</th>
                <th>Email</th>
                <th>Actions</th>
            </tr>
        </thead>
        <tbody>
            {% for user in users %}
            <tr>
                <td>{{ user.id }}</td>
                <td>{{ user.username }}</td>
                <td>{{ user.email }}</td>
                <td>
                    <a href="/user/{{ user.id }}">View</a>
                    <a href="/user/{{ user.id }}/edit">Edit</a>
                    <form method="POST" action="/user/{{ user.id }}/delete" style="display:inline">
                        <button type="submit">Delete</button>
                    </form>
                </td>
            </tr>
            {% endfor %}
        </tbody>
    </table>
</div>
{% endblock %}

Validation

The command checks:
  1. Entity Exists - Must be created first
  2. Repository Exists - Must be created with entity
  3. Controller Doesn’t Exist - Prevents overwriting

Error Examples

# Entity doesn't exist
$ framefox create:crud product
Failed to create controller. Entity or repository does not exist.

# Controller already exists
$ framefox create:crud user
Controller user already exists!

Customization

After generation, you can:
  1. Add Validation - Enhance form validators
  2. Add Filters - Filter list results
  3. Add Pagination - Paginate list view
  4. Add Search - Search functionality
  5. Add Authorization - Protect endpoints
  6. Customize Templates - Modify HTML/CSS

Best Practices

  • Use API controllers for SPAs and mobile apps
  • Use templated controllers for admin panels
  • Add authentication before exposing CRUD endpoints
  • Implement soft deletes instead of hard deletes
  • Add pagination for large datasets
  • Validate all input data
  • Use transactions for data integrity

Next Steps

Add Authentication

Secure your CRUD endpoints

Test Routes

View generated routes

Build docs developers (and LLMs) love