ApnaPHP

Documentation

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

  1. Keep routes simple - Use descriptive file names and organize logically
  2. Handle errors gracefully - Always check for null values and provide fallbacks
  3. Validate input - Use the validation system for user input
  4. Use appropriate HTTP methods - GET for reading, POST for creating, etc.
  5. Return proper status codes - 200 for success, 404 for not found, etc.
  6. Secure your routes - Use middleware for authentication and authorization
  7. Optimize for performance - Use caching and efficient database queries