Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 121 additions & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ JsWeb applications are configured using a `config.py` file in your project's roo

- [Config File Structure](#config-file-structure)
- [Core Configuration Options](#core-configuration-options)
- [OpenAPI & API Documentation Configuration](#openapi--api-documentation-configuration)
- [Database Configuration](#database-configuration)
- [Security Settings](#security-settings)
- [Development vs Production](#development-vs-production)
Expand All @@ -22,7 +23,11 @@ import os
# Get the base directory of the project
BASE_DIR = os.path.abspath(os.path.dirname(__file__))

# Secret key for signing sessions and other security-related things
# Application info
APP_NAME = "myproject"
VERSION = "0.1.0"

# Security
SECRET_KEY = "your-secret-key"

# Database configuration
Expand All @@ -35,6 +40,19 @@ STATIC_DIR = os.path.join(BASE_DIR, "static")

# Template files configuration
TEMPLATE_FOLDER = "templates"

# Server configuration
HOST = "127.0.0.1"
PORT = 8000

# OpenAPI / API Documentation (Automatic!)
ENABLE_OPENAPI_DOCS = True
API_TITLE = "myproject API"
API_VERSION = "1.0.0"
API_DESCRIPTION = "API documentation"
OPENAPI_DOCS_URL = "/docs"
OPENAPI_REDOC_URL = "/redoc"
OPENAPI_JSON_URL = "/openapi.json"
```

## Core Configuration Options
Expand All @@ -52,6 +70,108 @@ Here are the most important configuration options:
| `STATIC_DIR` | String | Directory path for static files |
| `TEMPLATE_FOLDER` | String | Directory for templates |
| `MAX_CONTENT_LENGTH` | Integer | Maximum upload file size in bytes |
| `BASE_DIR` | String | Absolute path to project root directory |
| `HOST` | String | Server host address (default: `127.0.0.1`) |
| `PORT` | Integer | Server port (default: `8000`) |
| `APP_NAME` | String | Application name |
| `VERSION` | String | Application version |

## OpenAPI & API Documentation Configuration

JsWeb automatically generates OpenAPI documentation with Swagger UI and ReDoc. Configure these settings to customize your API documentation:

```python
# OpenAPI / Swagger Documentation Configuration
ENABLE_OPENAPI_DOCS = True # Enable/disable automatic API documentation
API_TITLE = "My App API" # API title shown in documentation
API_VERSION = "1.0.0" # API version
API_DESCRIPTION = "API for my app" # API description (supports Markdown!)
OPENAPI_DOCS_URL = "/docs" # Swagger UI endpoint
OPENAPI_REDOC_URL = "/redoc" # ReDoc UI endpoint
OPENAPI_JSON_URL = "/openapi.json" # OpenAPI specification JSON endpoint
```

### API Documentation Settings

| Setting | Type | Default | Description |
|---------|------|---------|-------------|
| `ENABLE_OPENAPI_DOCS` | Boolean | `True` | Enable/disable automatic OpenAPI documentation |
| `API_TITLE` | String | `"jsweb API"` | Title displayed in API documentation |
| `API_VERSION` | String | `"1.0.0"` | Version of your API |
| `API_DESCRIPTION` | String | `""` | Description of your API (supports Markdown) |
| `OPENAPI_DOCS_URL` | String | `"/docs"` | URL endpoint for Swagger UI |
| `OPENAPI_REDOC_URL` | String | `"/redoc"` | URL endpoint for ReDoc UI |
| `OPENAPI_JSON_URL` | String | `"/openapi.json"` | URL endpoint for OpenAPI JSON specification |

### Using Markdown in API Description

The `API_DESCRIPTION` supports Markdown formatting for rich documentation:

```python
API_DESCRIPTION = """
# My Awesome API

This is a **production-ready** API built with JsWeb.

## Features

- Fast ASGI framework
- Automatic OpenAPI documentation
- Built-in authentication
- Database ORM support

## Base URL

`https://api.example.com`
"""
```

### Example: Complete API Documentation Setup

```python
# config.py
import os

# Core settings
APP_NAME = "Task Manager"
VERSION = "2.1.0"
SECRET_KEY = os.getenv('SECRET_KEY', 'dev-key')
DEBUG = os.getenv('DEBUG', False)

# Database
SQLALCHEMY_DATABASE_URI = "sqlite:///app.db"

# API Documentation
ENABLE_OPENAPI_DOCS = True
API_TITLE = "Task Manager API"
API_VERSION = "2.1.0"
API_DESCRIPTION = """
# Task Manager API

A powerful REST API for managing tasks and projects.

## Authentication

All endpoints require Bearer token authentication.

## Error Handling

Errors are returned as JSON with appropriate HTTP status codes.
"""
OPENAPI_DOCS_URL = "/api/docs"
OPENAPI_REDOC_URL = "/api/redoc"
OPENAPI_JSON_URL = "/api/spec.json"
```

### Disabling Documentation

To disable automatic API documentation:

```python
ENABLE_OPENAPI_DOCS = False
```

When disabled, the documentation endpoints will not be registered.

## Database Configuration

Expand Down
123 changes: 97 additions & 26 deletions docs/database.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ JsWeb provides a simple and powerful way to work with databases using [SQLAlchem
- [Configuration](#configuration)
- [Defining Models](#defining-models)
- [Model Fields](#model-fields)
- [Return Types](#return-types)
- [Querying the Database](#querying-the-database)
- [Database Migrations](#database-migrations)
- [Relationships](#relationships)
Expand Down Expand Up @@ -98,78 +99,148 @@ class Product(ModelBase):
category = Column(String(50), index=True)
```

## Return Types

ModelBase provides CRUD (Create, Read, Update, Delete) operations with the following return types:

### ModelBase Methods Return Types

| Method | Return Type | Description |
|--------|------------|-------------|
| `Model.create(**kwargs)` | `Model` | Creates and saves instance |
| `instance.save()` | `None` | Saves changes to database |
| `instance.update(**kwargs)` | `None` | Updates fields and saves |
| `instance.delete()` | `None` | Deletes instance from database |
| `Model.query.get(id)` | `Optional[Model]` | Get by primary key or None |
| `Model.query.first()` | `Optional[Model]` | Get first result or None |
| `Model.query.all()` | `List[Model]` | Get all results as list |
| `Model.query.filter_by()` | `Query` | Query builder |

### Example with Type Hints

```python
from typing import Optional, List
from jsweb.database import ModelBase

class User(ModelBase):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
username = Column(String(80), unique=True)
email = Column(String(120))

# Create - returns User instance
user: User = User.create(username="alice", email="[email protected]")

# Read - returns Optional[User]
user: Optional[User] = User.query.get(1)
user: Optional[User] = User.query.filter_by(username="bob").first()
users: List[User] = User.query.all()

# Update - returns None
user.email = "[email protected]"
user.save() # -> None

# Update with update() - returns None
user.update(email="[email protected]") # -> None

# Delete - returns None
user.delete() # -> None
```

## Querying the Database

### Get All Records

```python
from typing import List
from .models import User
from jsweb.response import render, HTMLResponse

@app.route("/users")
async def user_list(req):
users = User.query.all()
async def user_list(req) -> HTMLResponse:
users: List[User] = User.query.all()
return render(req, "users.html", {"users": users})
```

### Get a Single Record by ID

```python
from typing import Optional
from jsweb.response import render, json, HTMLResponse, JSONResponse

@app.route("/user/<int:user_id>")
async def user_detail(req, user_id):
user = User.query.get(user_id)
async def user_detail(req, user_id: int) -> HTMLResponse:
user: Optional[User] = User.query.get(user_id)
if user is None:
return "User not found", 404
return render(req, "404.html", {}), 404
return render(req, "user.html", {"user": user})

# JSON API version
@app.route("/api/user/<int:user_id>")
async def api_user_detail(req, user_id: int) -> JSONResponse:
user: Optional[User] = User.query.get(user_id)
if user is None:
return json({"error": "User not found"}, status_code=404)
return json(user.to_dict())
```

### Filter Records

```python
# Get user by username
user = User.query.filter_by(username="alice").first()
from typing import List, Optional
from sqlalchemy import or_

# Get all admin users
admins = User.query.filter_by(role="admin").all()
# Get user by username - returns Optional[User]
user: Optional[User] = User.query.filter_by(username="alice").first()

# Using filter() for more complex conditions
from sqlalchemy import or_
# Get all admin users - returns List[User]
admins: List[User] = User.query.filter_by(role="admin").all()

users = User.query.filter(
# Using filter() for more complex conditions - returns List[User]
users: List[User] = User.query.filter(
or_(User.username == "alice", User.email == "[email protected]")
).all()
```

!!! tip "first() vs all()"
- `first()` returns the first result or `None`
- `all()` returns a list of all results
- `get(id)` returns the record with that primary key or `None`
- `first()` returns `Optional[Model]` (first result or `None`)
- `all()` returns `List[Model]` (all results)
- `get(id)` returns `Optional[Model]` (by primary key or `None`)

### Creating Records

```python
# Method 1: Using create()
new_user = User.create(username="john", email="[email protected]")
# Method 1: Using create() - returns Model instance
new_user: User = User.create(username="john", email="[email protected]")

# Method 2: Creating and saving manually
# Method 2: Creating and saving manually - returns None
user = User(username="jane", email="[email protected]")
db.session.add(user)
db.session.commit()
user.save() # -> None
```

### Updating Records

```python
user = User.query.get(1)
user.email = "[email protected]"
db.session.commit()
# Method 1: Manual update - returns None
user: Optional[User] = User.query.get(1)
if user:
user.email = "[email protected]"
user.save() # -> None

# Method 2: Using update() - returns None
user.update(email="[email protected]") # -> None
```

### Deleting Records

```python
user = User.query.get(1)
db.session.delete(user)
db.session.commit()
# Method 1: Delete instance - returns None
user: Optional[User] = User.query.get(1)
if user:
user.delete() # -> None

# Method 2: Query and delete
User.query.filter_by(username="inactive").delete()
```

## Database Migrations
Expand Down
Loading