Whether you're building a startup product or an internal tool, your database design is the foundation of your application. Get it wrong, and you'll be fighting performance issues and data inconsistencies forever. Get it right, and scaling becomes much easier. Here's what I've learned building Laravel and PHP applications for clients in Bath, Bristol, and Wiltshire over the past 15 years.
Start Normalized, Denormalize When Needed
Begin with a properly normalized database. This means:
- No duplicate data
- Each piece of information stored once
- Relationships defined through foreign keys
- Data integrity enforced at the database level
Normalization prevents data inconsistencies and makes your database easier to maintain. Don't skip this step thinking you'll optimize later. It's much harder to normalize a messy database than to denormalize a clean one.
Index Strategically
Indexes are crucial for performance, but too many indexes slow down writes. Index:
- Foreign keys: Always. These are used in joins constantly.
- Columns used in WHERE clauses: If you frequently filter by status, index it.
- Columns used for sorting: If you often order by created_at, index it.
- Composite indexes: For queries that filter by multiple columns.
In Laravel, this is straightforward with migrations:
Schema::table('orders', function (Blueprint $table) {
$table->index(['user_id', 'status']); // Composite index
$table->index('created_at'); // For sorting
}); Use the Right Data Types
Choose appropriate data types. This affects both storage and performance:
- Use INT for IDs: Not VARCHAR. Much faster for joins.
- Use ENUM sparingly: They're fast but hard to change. Consider a statuses table instead.
- Use JSON columns wisely: Great for flexible data, but sometimes harder to query.
- Use TIMESTAMPS: Laravel's timestamps are perfect for most cases.
Plan for Soft Deletes
Most applications need to "delete" records without actually removing them. Laravel's soft deletes are perfect for this:
Schema::table('users', function (Blueprint $table) {
$table->softDeletes(); // Adds deleted_at column
});
// In your model
use SoftDeletes;
// Now $user->delete() sets deleted_at instead of removing the row This allows you to restore data, maintain referential integrity, and keep audit trails.
Handle Relationships Properly
Define relationships clearly in your database and in Laravel:
// Database: orders table has user_id foreign key
// Laravel model:
class Order extends Model
{
public function user()
{
return $this->belongsTo(User::class);
}
}
// Use eager loading to avoid N+1 queries:
$orders = Order::with('user')->get(); // One query, not N+1 When to Denormalize
Sometimes you need to denormalize for performance, especially at scale. Common cases:
- Counts: Store comment_count on posts table instead of counting every time
- Aggregates: Store total_amount on orders instead of summing line items
- Frequently accessed data: Store user name on orders for faster display
But as a general rule, don't do this too soon! Wait until it's needed.
You can use Laravel's model events to keep denormalized data in sync:
class Comment extends Model
{
protected static function booted()
{
static::created(function ($comment) {
$comment->post->increment('comment_count');
});
static::deleted(function ($comment) {
$comment->post->decrement('comment_count');
});
}
} Migration Strategy
Use Laravel migrations for all schema changes. This gives you:
- Version control for your database
- Easy deployment across environments
- Rollback capability
- Team collaboration
Never make manual database changes. Always use migrations.
Common Mistakes to Avoid
- Over-normalizing: Don't create tables for single columns
- Under-indexing: Your queries will be slow
- Ignoring foreign keys: They enforce data integrity
- Storing calculated values: Sometimes you need to, but be careful
- No timestamps: You'll want created_at/updated_at eventually
Real-World Problems I've Seen
Certain problems come up time and time again. Here are some of the most common ones I've seen:
- No indexes on foreign keys or frequently queried columns: Joins and lookups slow to a crawl as data grows. Add the indexes and the same queries often run many many times faster. It's not uncommon to see indexes alone drop page load times from many seconds to milliseconds.
- Over-normalized to the point where simple queries need many joins: Normalization is good, it's a solid first principle. But sometimes you have requirements that see you frequently performing many big joins, huge multiplicity kicks in and your database queries become very slow. Sometimes denormalizing a little is the best approach.
- Data grown too large: Databases designed in the early days of your product might not work so well as your application and it's dataset grow.
- Misuse of JSON columns instead of relationships: Useful in the right time and place for flexibility, but you lose referential integrity and it can be harder to query.
- Poor understanding of query planning: You can write SQL that's slow even if you've got the right database design and indexes. An understanding of how your database works and how it it runs queries (it's query plan) often allows small changes to deliver big results.
In each case, the fix is the same idea: get the schema and indexes right for how you actually write+query the data. It's harder to correct later than to design for it early. But problems will come up, so being able to recognise and addres these is important.
The Bottom Line
Good database design is invisible when it's right and painfully obvious when it's wrong. Take time to design your schema properly. Use migrations, add indexes, define relationships, and plan for growth. Your future self will thank you. For more on building backends that scale, see scaling your Laravel backend and API design. If you're looking for a Laravel or PHP developer in the area, see why hire a Laravel developer in Bath & Bristol.
If you're building an application in Bath, Bristol, or Wiltshire and need help with database design, get in touch. As a freelance developer I help startups and product teams across the UK build solid database foundations for their Laravel and PHP applications.