Best Practices

This final page doesn't introduce new features — it pulls together the most important habits from every lesson in this track into one capstone checklist. Treat it as a pre-launch review: before any CodeIgniter 4 project goes to production, walk down this list and confirm each item genuinely holds true for your codebase.

1. Only public/ Is Web-Exposed

As covered on the Directory Structure page, CodeIgniter's public/ folder is the only directory your webserver's document root should ever point at. Everything else — app/, vendor/, .env, writable/ — must sit outside what a browser can request directly.

# Apache/Nginx document root should be:
/my_project/public
# never:
/my_project
  • Confirm your virtual host / document root points at public/, not the project root
  • Double-check .env and app/ aren't reachable by requesting them directly in a browser
  • Never move controllers, models, or config files into public/ for "convenience"

2. Real Credentials Live in .env Only

The Database Configuration lesson covered keeping connection details out of version control. Extend that same discipline to every secret your app uses — API keys, mail credentials, third-party tokens — not just the database.

# .env — never committed to git
database.default.hostname = localhost
database.default.username = real_db_user
database.default.password = real_db_password
  • Confirm .env is listed in .gitignore
  • Never hardcode passwords or keys directly inside app/Config/*.php files that get committed
  • Use different .env files (and different credentials) for local, staging, and production

3. Query Builder or Bindings — Never Concatenated SQL

The Models lesson introduced the Query Builder specifically because it parameterizes values for you automatically, closing off SQL injection. Concatenating raw user input into a query string is the single most common way a CodeIgniter app gets compromised.

// Safe — Query Builder escapes and binds values automatically
$model->where('email', $email)->first();

// Dangerous — never build SQL by concatenating input
$db->query("SELECT * FROM users WHERE email = '" . $email . "'");
  • Prefer Query Builder methods (where(), find(), insert()) over raw query() calls
  • If raw SQL is unavoidable, always use bound parameters (? placeholders), never string concatenation
  • Never build a query string with a variable pulled straight from $_GET or $_POST

4. Escape Every Output with esc()

The Views lesson (and the Sessions & Security page's XSS discussion) both land on the same rule: anything that came from a user, a database row, or an external API must pass through esc() at the moment it's printed in a view.

// Safe
<h1><?= esc($article['title']) ?></h1>

// Unsafe — trusts the data is already clean, which you can never guarantee
<h1><?= $article['title'] ?></h1>
  • Never echo or <?= ?> raw user-influenced data without esc()
  • Remember data from your own database still needs escaping — it may have entered unescaped via another path
  • Use the correct esc() context ('html', 'attr', 'js') when displaying inside something other than plain HTML

5. Validate Everything, Trust Nothing from $_POST/$_GET

The Form Validation page's central lesson: run every piece of incoming form or API data through the Validation library before it touches your database or business logic, even data you "control" through your own frontend.

if (!$this->validate([
    'email' => 'required|valid_email',
])) {
    return redirect()->back()->withInput()->with('errors', $this->validator->getErrors());
}
  • Never insert or update a row using raw $this->request->getPost() values without validating first
  • Validate API JSON bodies with validateData() just as strictly as HTML form submissions
  • Centralize rules used in more than one place in app/Config/Validation.php named groups

6. Keep CSRF Protection Enabled

From the Sessions & Security page: leave the csrf filter active in app/Config/Filters.php for the entire life of the project, and make csrf_field() a reflex on every form you write.

<form method="post" action="/login">
  <?= csrf_field() ?>
  <!-- fields -->
</form>
  • Never disable the global csrf filter to "fix" a failing form — fix the missing csrf_field() instead
  • Double-check AJAX form submissions also send the current CSRF token
  • Re-verify CSRF is still active after any changes to app/Config/Filters.php

7. Keep Controllers Thin

A controller's job is to receive the request, delegate the actual work, and return a response — not to contain business logic itself. Query logic belongs in Models, as covered on the Models & Query Builder page.

// Thin controller — delegates to the Model
public function index()
{
    $model = new ArticleModel();
    return view('articles/index', ['articles' => $model->orderBy('created_at', 'DESC')->findAll()]);
}
  • If a controller method is doing multi-step data manipulation, move that logic into the Model
  • Reuse Model methods across controllers instead of duplicating query logic in each one
  • A controller method that's hard to summarize in one sentence is usually doing too much

8. Consistent JSON Shapes and Real HTTP Status Codes

From the REST API page: every endpoint should set the status code that actually matches what happened, and every response — success or error — should follow the same predictable JSON shape so API consumers can handle responses generically.

// Consistent success shape
return $this->response->setStatusCode(201)->setJSON(['data' => $article]);

// Consistent error shape
return $this->response->setStatusCode(404)->setJSON(['error' => 'Article not found']);
  • Never return 200 OK for a request that actually failed
  • Pick one shape for success (e.g. always under a data key) and one for errors, and use them everywhere
  • Document expected status codes for each endpoint so API consumers can rely on them
Note: None of these eight items require exotic tooling or advanced configuration — every one of them is a habit you already have the knowledge to apply, straight from the earlier lessons in this track.

That completes the CodeIgniter 4 track, from installing your first project all the way through building a secured, validated JSON API. Congratulations on working through the whole thing — routing, MVC structure, the Query Builder, form validation, sessions, and REST APIs are not CodeIgniter-specific ideas. They're the same core concepts you'll recognize immediately in CakePHP, Laravel, and Symfony (some of which are already covered elsewhere on this site) — only the syntax and conventions change from framework to framework. The "Next" button below leads back to the PHP hub, where you can pick up another track and keep building on what you've learned here.