How we structure a new CodeIgniter build
The schema comes first
We start from the data, because that is the part that outlives every screen. Tables, keys and indexes are drafted with you in a diagram, then written as migrations so the database can be rebuilt from an empty server with one Spark command. Seed classes create the reference data and a set of realistic test accounts. From that point nobody edits the database by hand, on staging or in production.
Thin controllers, plain models, rules in between
The most common way a CodeIgniter application goes bad is that controllers absorb everything: validation, queries, emails and calculations in one method. We keep controllers to a few lines. They read the request, call a service class and return a view or a redirect. Models handle reading and writing rows. The rules that make your business yours, such as how a quote is priced or when an order may be cancelled, live in service classes with no knowledge of HTTP. That separation is what lets us test them and reuse them later from an API built on the same CodeIgniter project.
Validation at the edge, escaping at the output
Every form and endpoint has a named rule set. Data that fails never reaches a model. Models also declare which fields may be written, so a crafted request cannot set a column the form never offered. On the way out, views escape values by context. Queries go through the Query Builder or prepared statements, never through strings glued together with user input.
Sessions and authentication that match the hosting
File sessions are fine on one server. Once there are two servers, or a host that cleans temporary folders aggressively, we move sessions to the database or Redis. For logins we normally use Shield, the authentication library maintained by the CodeIgniter team, and add groups and permissions that mirror your real job roles. Access rules are enforced in route filters and checked again in the service layer, since hiding a menu item is no protection.
Deployment without drama
CodeIgniter 4 keeps the web root in a public folder, so application code and environment files stay outside the reach of a browser. We set that up correctly even on shared hosting, where it is often skipped. Each environment has its own settings file, error display is off in production, and logs go to the writable folder, with old files cleared on a schedule. A deployment is a script: pull the code, install Composer packages, run migrations, clear caches. If your host only offers FTP we adapt, and we tell you what you give up.