Foreign keys enforce referential integrity between related tables
Why it matters
Without foreign key constraints, the database allows orphan rows — comments that reference deleted posts, orders that reference nonexistent users, line items whose product was removed. Application code must then paper over these inconsistencies with defensive null checks, producing unpredictable behavior in edge cases. Missing ON DELETE clauses mean the database default (NO ACTION) silently blocks parent deletes or, in some configurations, leaves orphans. ISO 25010 functional-correctness requires that referential constraints are enforced at the storage layer, not assumed from application logic that can be bypassed by direct SQL access, background jobs, or migration scripts.
Severity rationale
Critical because orphan rows corrupt business-critical data relationships and can only be detected after the fact, often requiring expensive reconciliation queries.
Remediation
Add @relation with explicit onDelete in Prisma, or FOREIGN KEY ... REFERENCES ... ON DELETE in raw SQL, for every column that logically points to another table.
model Comment {
id String @id @default(uuid())
postId String
post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
}
ALTER TABLE orders
ADD CONSTRAINT fk_orders_user
FOREIGN KEY (user_id)
REFERENCES users(id)
ON DELETE RESTRICT;
Choose CASCADE for owned data (comments belong to a post), SET NULL for optional references, and RESTRICT for financial or audit records where parent deletion must be blocked.
Detection
-
ID:
foreign-keys-present -
Severity:
critical -
What to look for: Count every column that is named with an
_idsuffix or that stores a reference to another table (e.g.,userId,post_id,order_id,organization_id). For each such column, classify it as "has FK constraint" or "missing FK constraint." In Prisma, look for@relationdecorators. In raw SQL, look forREFERENCES table_name(column)andFOREIGN KEYsyntax. In Drizzle, look for.references(() => table.column). In TypeORM, look for@ManyToOne,@OneToMany,@ManyToManydecorators. Also check whetherON DELETEbehavior is explicitly defined — CASCADE, SET NULL, or RESTRICT/NO ACTION. Missing ON DELETE behavior means accidental orphan records if parent rows are deleted. -
Pass criteria: All columns that logically reference another table have FK constraints declared — at least 1 ON DELETE clause per FK. ON DELETE behavior is explicitly defined for every FK (not left to database default). Prisma
@relationis present for all relationships. Raw SQL usesFOREIGN KEY ... REFERENCES ... ON DELETE. Report even on pass: "X FK columns found, all with explicit ON DELETE behavior." -
Cross-reference: For query performance on FK columns, see the
indexes-on-queriescheck in the Schema Design category — unindexed FK columns cause full table scans on JOINs. -
Fail criteria: Columns named
*_idexist without FK constraints. Relationships tracked only at the application layer (e.g.,userIdfield with no DB-level constraint). Any FK is missing itsON DELETEclause. -
Skip (N/A) when: Single-table application with no relationships between entities (extremely rare in real projects).
-
Detail on fail: Specify which relationships are unenforced. Example:
"'comments' table has 'post_id' and 'user_id' columns with no FK constraints in schema.prisma — orphan comments possible."or"FK exists on 'orders.user_id' but ON DELETE behavior is undefined — Postgres will default to NO ACTION.". -
Remediation: Add FK constraints with explicit ON DELETE behavior:
// Prisma — one-to-many relationship with cascade delete model Comment { id String @id @default(uuid()) postId String post Post @relation(fields: [postId], references: [id], onDelete: Cascade) } model Post { id String @id @default(uuid()) comments Comment[] }-- Raw SQL — FK with explicit ON DELETE ALTER TABLE comments ADD CONSTRAINT fk_comments_post FOREIGN KEY (post_id) REFERENCES posts(id) ON DELETE CASCADE; ALTER TABLE orders ADD CONSTRAINT fk_orders_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE RESTRICT; -- Prevent deleting users who have ordersChoose ON DELETE behavior intentionally:
CASCADE— delete child rows when parent is deleted (good for owned data like comments)SET NULL— nullify the FK column (good for optional relationships)RESTRICT/NO ACTION— block parent deletion if children exist (good for financial records)
External references
- iso-25010:2011 · functional-suitability.functional-correctness — Functional Correctness
Taxons
History
- 2026-04-18·v1.0.0·Initial import from database-design-operations·automated