Follow along with the code samples by typing them into your editor to better understand how Expressify works.
Browse examplesEverything you need to know to get up and running with Expressify - the Express.js-inspired framework for Python.
Expressify requires Python 3.7 or higher. You can install it using pip:
pip install expressify
Or install from source:
git clone https://github.com/itsdhruvrawat/expressify.git
cd expressify
pip install -e .
Make sure to have the latest version of pip installed to avoid any compatibility issues.
Let's create a simple "Hello, World!" application with Expressify:
from expressify import expressify
# Create a new application
app = expressify()
# Define a route for the home page
@app.get('/')
def home(req, res):
res.send('Hello, World!')
# Start the server
if __name__ == '__main__':
app.listen(port=3000, hostname='127.0.0.1') # Defaults to 127.0.0.1:3000
Save this code to a file named app.py and run it:
python app.py
Expressify server running on http://127.0.0.1:3000
Press CTRL+C to quit
Open your web browser and navigate to http://127.0.0.1:3000 to see "Hello, World!" displayed.
Expressify includes a development server with hot reloading capabilities to make your development workflow more efficient. When you run your application with dev.py, it automatically detects file changes and restarts your application in real-time.
š„ Expressify development server starting... š Application: app.py š Starting application: app.py š¤ Server running at http://127.0.0.1:3000 š¤ Press Ctrl+C to stop the server ... š File changed: app.py š Reloading application... š Stopping current process... š Starting application: app.py š¤ Server running at http://127.0.0.1:3000 ...
Behind the scenes, the development server uses Python's watchdog package to monitor file changes and automatically restarts your application when any Python file is modified.
If you want to use the development server in your own project without installing the full Expressify package, you can copy the following code into a file named dev.py:
#!/usr/bin/env python3
"""
Expressify Development Server with Hot Reloading
This script provides a development server with automatic reloading
when source files change. It monitors the current directory and restarts
the application when any Python file changes.
Usage:
python dev.py app.py
python dev.py examples/01-hello-world/app.py
"""
import argparse
import importlib.util
import os
import signal
import subprocess
import sys
import time
from pathlib import Path
from types import ModuleType
from typing import Any, List, Optional
try:
from watchdog.events import FileSystemEventHandler
from watchdog.observers import Observer
except ImportError:
print("watchdog package is required for dev server. Install it with:")
print("pip install watchdog")
sys.exit(1)
class AppReloader(FileSystemEventHandler):
"""Watches for file changes and reloads the application."""
def __init__(self, app_path: str, watch_dirs: List[str] = None):
self.app_path = app_path
self.watch_dirs = watch_dirs or ['.']
self.process: Optional[subprocess.Popen] = None
self.observer = Observer()
self.module_name = os.path.basename(app_path).replace('.py', '')
self.module: Optional[ModuleType] = None
self.last_reload = 0
self.reloading = False
# Handle SIGINT (Ctrl+C) gracefully
signal.signal(signal.SIGINT, self.handle_sigint)
def start(self):
"""Start the development server with hot reloading."""
print(f"š„ Expressify development server starting...")
print(f"š Application: {self.app_path}")
# Start watching for file changes
for watch_dir in self.watch_dirs:
self.observer.schedule(self, watch_dir, recursive=True)
self.observer.start()
try:
self.load_app()
while True:
time.sleep(1)
except KeyboardInterrupt:
self.stop()
def stop(self):
"""Stop the reloader and clean up resources."""
print("\nš Shutting down development server...")
if self.process:
self.kill_process()
self.observer.stop()
self.observer.join()
print("š Server stopped")
def handle_sigint(self, sig, frame):
"""Handle SIGINT (Ctrl+C) signal."""
self.stop()
sys.exit(0)
def on_modified(self, event):
"""Called when a file is modified."""
if event.is_directory:
return
# Only reload on Python file changes
if not event.src_path.endswith('.py'):
return
# Avoid multiple simultaneous reloads
current_time = time.time()
if current_time - self.last_reload < 1 or self.reloading:
return
self.last_reload = current_time
self.reloading = True
print(f"\nš File changed: {os.path.basename(event.src_path)}")
self.reload_app()
self.reloading = False
def load_app(self):
"""Load or reload the application."""
if self.process:
self.kill_process()
print(f"š Starting application: {self.app_path}")
# Start the application as a subprocess
try:
self.process = subprocess.Popen(
[sys.executable, self.app_path],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
bufsize=1 # Line buffered
)
# Start threads to read stdout and stderr
self.start_output_reader(self.process.stdout, "š¤")
self.start_output_reader(self.process.stderr, "ā ļø")
except Exception as e:
print(f"ā Failed to start application: {e}")
self.process = None
def reload_app(self):
"""Reload the application after changes."""
print("š Reloading application...")
self.load_app()
def kill_process(self):
"""Kill the current process if it exists."""
if self.process:
print("š Stopping current process...")
try:
# Try to terminate gracefully first
self.process.terminate()
# Give it some time to terminate
for _ in range(5):
if self.process.poll() is not None:
break
time.sleep(0.1)
# If still running, force kill
if self.process.poll() is None:
self.process.kill()
self.process.wait()
except Exception as e:
print(f"ā Error stopping process: {e}")
self.process = None
def start_output_reader(self, pipe, prefix):
"""Start a thread to read output from the subprocess."""
def reader():
for line in iter(pipe.readline, ''):
if line.strip():
print(f"{prefix} {line.rstrip()}")
import threading
thread = threading.Thread(target=reader)
thread.daemon = True
thread.start()
def find_watch_dirs(app_path: str) -> List[str]:
"""Find directories to watch for changes."""
app_dir = os.path.dirname(os.path.abspath(app_path))
# Always watch the directory of the app
watch_dirs = [app_dir]
# If it's an examples subdirectory, also watch the core expressify package
if app_dir.startswith(os.path.join(os.getcwd(), 'examples')):
expressify_dir = os.path.join(os.getcwd(), 'expressify')
if os.path.exists(expressify_dir):
watch_dirs.append(expressify_dir)
return watch_dirs
def main():
"""Main entry point for the development server."""
parser = argparse.ArgumentParser(description="Expressify Development Server with Hot Reloading")
parser.add_argument('app_path', help="Path to the main application file")
args = parser.parse_args()
# Ensure the app file exists
if not os.path.exists(args.app_path):
print(f"ā Error: App file not found: {args.app_path}")
sys.exit(1)
# Find directories to watch
watch_dirs = find_watch_dirs(args.app_path)
# Start the reloader
reloader = AppReloader(args.app_path, watch_dirs)
reloader.start()
if __name__ == "__main__":
main()
To use this script:
dev.py in your projectpip install watchdogchmod +x dev.py (Linux/macOS only)python dev.py app.pyMiddleware functions are functions that have access to the request object, response object, and the next middleware function in the application's request-response cycle. Middleware can:
Global middleware is applied to all routes in your application. This is useful for functionality like logging, authentication, or setting common headers.
from expressify import Expressify
app = Expressify()
@app.use
def logger_middleware(req, res, next):
"""Log request information"""
print(f"{req.method} {req.path}")
# Call the next middleware/route handler
result = next()
# Log the response status code
print(f"Response: {res.status_code}")
# Return the result
return result
@app.get('/')
def home(req, res):
return res.send('Home page')
if __name__ == '__main__':
app.listen(port=3000)
from expressify import expressify
app = expressify()
def logger_middleware(req, res, next):
print(f"{req.method} {req.path}")
next()
# Apply middleware globally
app.use(logger_middleware)
def home(req, res):
res.send('Home page')
app.get('/', home)
if __name__ == '__main__':
app.listen(port=3000)
Multiple middleware functions can be chained together. Each middleware has access to the request and response objects, and can either pass control to the next middleware using next() or terminate the request-response cycle.
from expressify import Expressify
import time
import json
app = Expressify()
# Define multiple middleware functions
@app.use
def logger_middleware(req, res, next):
"""Log request information and timing data"""
start_time = time.time()
print(f"Request: {req.method} {req.path}")
result = next()
duration = (time.time() - start_time) * 1000 # Convert to ms
print(f"Response: {res.status_code} - {duration:.2f}ms")
return result
@app.use
def cors_middleware(req, res, next):
"""Add CORS headers to enable cross-origin requests"""
res.set_header('Access-Control-Allow-Origin', '*')
res.set_header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
res.set_header('Access-Control-Allow-Headers', 'Content-Type, Authorization')
if req.method == 'OPTIONS':
return res.status(204).send()
return next()
@app.use
def json_parser_middleware(req, res, next):
"""Parse JSON request bodies"""
if 'application/json' in req.get_header('content-type', ''):
try:
if req.body:
req.json = json.loads(req.body)
except json.JSONDecodeError:
return res.status(400).json({
'error': 'Invalid JSON',
'message': 'Failed to parse request body as JSON'
})
return next()
@app.get('/')
def home(req, res):
return res.send('Hello from Expressify!')
@app.post('/api/data')
def create_data(req, res):
# The JSON parser middleware will have already parsed the JSON body
if hasattr(req, 'json'):
return res.status(201).json({
'message': 'Data received successfully',
'data': req.json
})
else:
return res.status(400).json({
'error': 'No data provided',
'message': 'Request body should contain JSON data'
})
if __name__ == '__main__':
app.listen(port=3000)
Middleware execution follows the order in which they are defined. Global middleware defined with app.use() runs first, followed by route-specific middleware, and finally the route handler.
Verify user identity and protect routes that require authentication. Authentication middleware can check for tokens, sessions, or other credentials.
Record request information, response times, and other metrics. Logging middleware can be valuable for debugging and monitoring application performance.
Handle Cross-Origin Resource Sharing to control which domains can access your API. CORS middleware sets appropriate headers for cross-domain requests.
Compress HTTP responses to reduce bandwidth and improve loading times. Compression middleware typically uses gzip or other algorithms to reduce payload size.
Expressify includes built-in support for Jinja2 templates:
import os
from expressify import expressify
app = expressify()
# Set up the template engine
app.set('view engine', 'jinja2')
app.set('views', os.path.join(os.path.dirname(__file__), 'views'))
@app.get('/')
def home(req, res):
# Render the index.html template with data
res.render('index.html', {
'title': 'Expressify Example',
'message': 'Welcome to Expressify!',
'items': ['Item 1', 'Item 2', 'Item 3']
})
Create a template file views/index.html:
<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
<h1>{{ message }}</h1>
<ul>
{% for item in items %}
<li>{{ item }}</li>
{% endfor %}
</ul>
</body>
</html>
Expressify makes it easy to serve static files like CSS, JavaScript, and images:
from expressify import expressify
from expressify.lib.middleware import Middleware
import os
app = expressify()
# Serve static files from the 'public' directory
app.use('/static', Middleware.static(os.path.join(os.path.dirname(__file__), 'public')))
# Now you can access files at /static/css/style.css, /static/js/script.js, etc.
Now that you've got the basics, explore the full capabilities of Expressify:
You've learned the basics of Expressify. Now it's time to build your own application!
Explore Examples