Routing
ApnaPHP uses a file-based routing system inspired by Next.js App Router, making it intuitive to create routes by simply organizing your files.
File-Based Routing
In ApnaPHP, the file system is the router. Each file in the app/ directory automatically becomes a route based on its location and name.
Basic Pages
Create a page by adding a page.apna.php file:
app/
├── page.apna.php # → /
├── about/
│ └── page.apna.php # → /about
└── contact/
└── page.apna.php # → /contact
API Routes
Create API endpoints with route.apna.php:
app/
├── api/
│ ├── users/
│ │ └── route.apna.php # → /api/users
│ └── posts/
│ └── route.apna.php # → /api/posts
Dynamic Routes
Single Parameter
Use square brackets [param] to create dynamic segments:
app/
├── blog/
│ ├── [slug]/
│ │ └── page.apna.php # → /blog/[slug]
│ └── page.apna.php # → /blog
<?php
// app/blog/[slug]/page.apna.php
$slug = request()->param('slug');
$post = Post::where('slug', $slug)->first();
if (!$post) {
return notFound();
}
?>
<h1><?= htmlspecialchars($post->title) ?></h1>
<p><?= $post->content ?></p>
Multiple Parameters
You can have multiple dynamic segments:
app/
├── blog/
│ └── [year]/
│ └── [month]/
│ └── [slug]/
│ └── page.apna.php # → /blog/[year]/[month]/[slug]
<?php
// app/blog/[year]/[month]/[slug]/page.apna.php
$year = request()->param('year');
$month = request()->param('month');
$slug = request()->param('slug');
$post = Post::where('year', $year)
->where('month', $month)
->where('slug', $slug)
->first();
?>
Catch-All Routes
Use [...slug] for catch-all routes:
app/
├── docs/
│ └── [...slug]/
│ └── page.apna.php # → /docs/[...slug]
<?php
// app/docs/[...slug]/page.apna.php
$segments = request()->params(); // Returns array of all segments
$docPath = implode('/', $segments);
$content = loadDocContent($docPath);
?>
Route Groups
Use parentheses (group) to organize routes without affecting the URL:
app/
├── (admin)/
│ ├── dashboard/
│ │ └── page.apna.php # → /dashboard (not /admin/dashboard)
│ └── users/
│ └── page.apna.php # → /users (not /admin/users)
└── (public)/
├── about/
│ └── page.apna.php # → /about
└── contact/
└── page.apna.php # → /contact
API Routes
API routes are created using route.apna.php files and return JSON responses:
<?php
// app/api/users/route.apna.php
class UsersAPI {
// GET /api/users
public function GET() {
$users = User::all();
return json(['users' => $users]);
}
// POST /api/users
public function POST() {
$data = request()->json();
$user = User::create([
'name' => $data['name'],
'email' => $data['email']
]);
return json(['user' => $user], 201);
}
// PUT /api/users
public function PUT() {
$data = request()->json();
$user = User::find($data['id']);
if (!$user) {
return json(['error' => 'User not found'], 404);
}
$user->update($data);
return json(['user' => $user]);
}
// DELETE /api/users
public function DELETE() {
$data = request()->json();
$user = User::find($data['id']);
if (!$user) {
return json(['error' => 'User not found'], 404);
}
$user->delete();
return json(['message' => 'User deleted']);
}
}
API with Parameters
<?php
// app/api/users/[id]/route.apna.php
class UserAPI {
// GET /api/users/123
public function GET() {
$id = request()->param('id');
$user = User::find($id);
if (!$user) {
return json(['error' => 'User not found'], 404);
}
return json(['user' => $user]);
}
// PUT /api/users/123
public function PUT() {
$id = request()->param('id');
$data = request()->json();
$user = User::find($id);
if (!$user) {
return json(['error' => 'User not found'], 404);
}
$user->update($data);
return json(['user' => $user]);
}
// DELETE /api/users/123
public function DELETE() {
$id = request()->param('id');
$user = User::find($id);
if (!$user) {
return json(['error' => 'User not found'], 404);
}
$user->delete();
return json(['message' => 'User deleted']);
}
}
Request Object
Access request data using the global request() helper:
<?php
// Get request parameters
$id = request()->param('id');
$slug = request()->param('slug');
// Get query parameters
$page = request()->query('page', 1); // with default
$search = request()->query('search');
// Get POST data
$name = request()->post('name');
$email = request()->post('email');
// Get JSON data
$data = request()->json();
// Get all input data
$all = request()->all();
// Check if input exists
if (request()->has('name')) {
// Handle name parameter
}
// Get request method
$method = request()->method(); // GET, POST, PUT, DELETE
// Check request method
if (request()->isPost()) {
// Handle POST request
}
// Get request path
$path = request()->path(); // /blog/my-post
// Get URL segments
$segments = request()->segments(); // ['blog', 'my-post']
$firstSegment = request()->segment(1); // 'blog'
// Get uploaded files
$file = request()->file('avatar');
if (request()->hasFile('avatar')) {
// Handle file upload
}
// Get headers
$userAgent = request()->header('User-Agent');
$authorization = request()->header('Authorization');
// Get bearer token
$token = request()->bearerToken();
// Get client IP
$ip = request()->ip();
// Check if AJAX request
if (request()->ajax()) {
// Handle AJAX request
}
// Check if expects JSON
if (request()->expectsJson()) {
// Return JSON response
}
Response Object
Create responses using helper functions:
<?php
// JSON response
return json(['message' => 'Success']);
// HTML response
return html('<h1>Hello World</h1>');
// Redirect response
return redirect('/dashboard');
// Redirect with flash message
return redirect('/login')->withError('Please login first');
// Redirect back
return redirect()->back();
// File download
return response()->download('/path/to/file.pdf');
// File serving
return response()->file('/path/to/image.jpg');
// Custom response
return response()
->html('<h1>Custom Response</h1>')
->status(201)
->header('X-Custom-Header', 'value');
// Error responses
return notFound();
return serverError('Something went wrong');
return abort(403, 'Access denied');
Middleware
Middleware runs before your route handler and can modify the request or response:
<?php
// app/middleware.apna.php (global middleware)
// Authentication check
if (!auth()->check() && !request()->is('/login')) {
return redirect('/login');
}
// Rate limiting
$key = request()->ip();
$attempts = cache()->get("rate_limit_{$key}", 0);
if ($attempts > 100) {
return abort(429, 'Too many requests');
}
cache()->put("rate_limit_{$key}", $attempts + 1, 60);
// Add security headers
response()->headers([
'X-Frame-Options' => 'DENY',
'X-XSS-Protection' => '1; mode=block',
'X-Content-Type-Options' => 'nosniff'
]);
Route-Specific Middleware
<?php
// app/admin/middleware.apna.php (admin section middleware)
// Check admin role
if (!auth()->user() || !auth()->user()->isAdmin()) {
return abort(403, 'Admin access required');
}
// Log admin actions
console_info('Admin access', [
'user' => auth()->user()->name,
'path' => request()->path(),
'ip' => request()->ip()
]);
Error Handling
404 Not Found
Create a not-found.apna.php file at any level:
<?php
// app/not-found.apna.php (global 404)
metadata(['title' => 'Page Not Found']);
?>
<h1>404 - Page Not Found</h1>
<p>The page you're looking for doesn't exist.</p>
<a href="/">Go Home</a>
<?php
// app/blog/not-found.apna.php (blog section 404)
metadata(['title' => 'Blog Post Not Found']);
?>
<h1>Blog Post Not Found</h1>
<p>Sorry, we couldn't find that blog post.</p>
<a href="/blog">Browse All Posts</a>
500 Server Error
<?php
// app/500.apna.php
metadata(['title' => 'Server Error']);
?>
<h1>500 - Server Error</h1>
<p>Something went wrong on our end.</p>
Custom Error Pages
<?php
// app/503.apna.php (maintenance mode)
metadata(['title' => 'Under Maintenance']);
?>
<h1>We'll Be Right Back</h1>
<p>We're currently performing maintenance. Please check back later.</p>
Route Caching
ApnaPHP automatically caches routes for better performance in production:
# Clear route cache
php apna route:clear
# List all routes
php apna routes
Best Practices
- Keep routes simple - Use descriptive file names and organize logically
- Handle errors gracefully - Always check for null values and provide fallbacks
- Validate input - Use the validation system for user input
- Use appropriate HTTP methods - GET for reading, POST for creating, etc.
- Return proper status codes - 200 for success, 404 for not found, etc.
- Secure your routes - Use middleware for authentication and authorization
- Optimize for performance - Use caching and efficient database queries
