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 watchdog
chmod +x dev.py
(Linux/macOS only)python dev.py app.py
Middleware 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