How to Connect a Website to a Database Using PHP and MySQL
The moment a website needs to remember anything, such as contact form messages, products, or user accounts, static HTML stops being enough. Connecting a website to a database means letting server-side code, in this case PHP, open an authenticated link to a MySQL server so it can read and write structured data on each request. It is one of the oldest patterns on the web and still powers a huge share of sites, but most copy-paste tutorials teach it in ways that leak passwords or invite SQL injection. This guide shows the modern, safe way to do it.
Quick Answer: To connect a website to MySQL with PHP, create a database and a dedicated user, store the credentials in a config file outside the public web folder, connect using PDO with exceptions enabled and the utf8mb4 charset, and run every query that includes user input as a prepared statement with bound parameters.
How PHP and MySQL Work Together
PHP and MySQL work together as a request-response pair: the browser asks the web server for a page, PHP runs on the server, talks to MySQL, and returns finished HTML. The browser never connects to the database directly, and it never sees your credentials. That separation is the entire security model, so anything that blurs it, such as putting database logic in JavaScript, is a red flag.
There are three moving parts. The web server (Apache or Nginx) receives the request and hands .php files to the PHP interpreter. The PHP script opens a connection using a driver. The MySQL server (or its compatible fork, MariaDB) checks the username and password, then executes queries. Where these pieces run depends heavily on your hosting type, and if you are still deciding, this breakdown of shared hosting vs a VPS explains how much database control each option gives you.
PHP offers two drivers for MySQL today. MySQLi is MySQL-specific and supports both procedural and object-oriented styles. PDO (PHP Data Objects) is a database-agnostic layer that works with MySQL, PostgreSQL, SQLite, and others through the same API. The old mysql_* functions were removed entirely in PHP 7.0, so any tutorial still using mysql_connect() is out of date and should be ignored.
What You Need Before Writing Any Code
Before writing connection code, you need a working environment and a database with its own restricted user. Skipping the setup steps is the main reason beginners end up connecting as the all-powerful root account.
- A local stack: XAMPP, MAMP, Laragon, or Docker give you PHP, MySQL or MariaDB, and a web server in one install. On a host, these are usually preinstalled.
- A database: Create one through phpMyAdmin, your hosting control panel, or the command line with
CREATE DATABASE site_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;. - A dedicated database user: Create a user that only has access to this one database, and only the privileges your site needs, typically SELECT, INSERT, UPDATE, and DELETE.
- A table: Start small. A
messagestable withid,name,email,body, andcreated_atcolumns is enough to learn the full read and write cycle. - A config file outside the web root: Store host, database name, username, and password in a file the web server will never serve as plain text, or use environment variables.
- PHP 8 or newer: Modern PHP throws exceptions from PDO by default and includes better type handling, which makes errors easier to find.
The utf8mb4 character set deserves a note. MySQL's older utf8 encoding only stores up to three bytes per character, which breaks on emoji and some scripts. Using utf8mb4 from the start avoids a painful migration later when a user submits a message containing characters your database cannot store.
PDO vs MySQLi: Which Should You Use?
For most new projects, PDO is the better default, although MySQLi is a perfectly valid, secure choice. The comparison below reflects how each behaves in day-to-day work rather than benchmark trivia.
| Feature | PDO | MySQLi |
|---|---|---|
| Databases supported | MySQL, PostgreSQL, SQLite, SQL Server, and more | MySQL and MariaDB only |
| Named placeholders | Yes, such as :email | No, positional ? only |
| Coding style | Object-oriented | Object-oriented or procedural |
| Default error behavior in PHP 8 | Throws exceptions | Throws exceptions since PHP 8.1 |
| MySQL-specific features | Common features covered | Broader access to MySQL-only features |
Named placeholders are the practical tiebreaker for beginners. A query like WHERE email = :email AND status = :status is easier to read and harder to misorder than a row of question marks. If you ever move a project to PostgreSQL or SQLite, PDO code also needs far fewer changes.
Why Prepared Statements Matter More Than Anything Else
Prepared statements are the single most important habit in PHP database work because they separate SQL code from user data. This section is practitioner analysis backed by well-established security guidance.
Injection has been a fixture of the OWASP Top 10 list of web application risks for as long as that list has existed. The classic attack is simple: a form field is pasted directly into a SQL string, and an attacker types something like a quote followed by OR 1=1 to change the meaning of the query. With string concatenation, the database cannot tell your SQL from the attacker's text. With a prepared statement, the query structure is sent first and the values are sent separately, so user input is always treated as data, never as code.
In practice, I see three patterns on hacked PHP sites again and again: concatenated queries in old contact forms, credentials committed in a public repository, and a database user with full privileges over every database on the server. Fixing all three takes an afternoon. Prepared statements also make your code cleaner, because you stop escaping quotes by hand.
Passwords deserve the same care. Never store them as plain text in MySQL; use PHP's built-in password_hash() and password_verify() functions, which handle salting automatically. The same principles apply in any stack, as shown in this Node.js login system walkthrough that uses bcrypt for the identical job.
Common Mistakes When Connecting PHP to MySQL
Most PHP database bugs and breaches trace back to a handful of avoidable mistakes. Check your project against this list before it goes live.
- Connecting as root. If your site is compromised, the attacker inherits every privilege. Create a limited user per site.
- Displaying raw database errors. Messages that print the query, table names, or credentials help attackers. Log detailed errors to a file and show users a generic message.
- Keeping config files in the public folder. If PHP ever stops processing files due to a misconfiguration, a
config.phpin the web root can be served as plain text. Store it one level above. - Opening a new connection per query. Create one PDO object per request and reuse it. Repeated connections waste time and can hit your host's connection limit.
- Using emulated prepares without thinking. PDO's MySQL driver emulates prepared statements by default. It is safe when the charset is set in the DSN, but setting
PDO::ATTR_EMULATE_PREPARESto false gives you native prepares and stricter typing. - Forgetting the charset in the DSN. Leaving out
charset=utf8mb4leads to garbled text and subtle encoding bugs. - Not escaping output. Prepared statements protect the database, but you still need
htmlspecialchars()when printing stored data into HTML to prevent cross-site scripting. - Hardcoding localhost assumptions. Many hosts use a separate database hostname. Read it from config instead of assuming
localhost.
Step-by-Step: Building a Working Connection and Contact Form
The quickest way to learn the pattern is to build a contact form that saves messages and lists them back. Follow these steps in order on your local stack.
Step 1: Create the table. In phpMyAdmin or the MySQL console, run a CREATE TABLE messages statement with an auto-increment primary key, VARCHAR columns for name and email, a TEXT column for the body, and a created_at column defaulting to the current timestamp.
Step 2: Write the config file. Outside your public folder, create config.php returning an array with host, db, user, and pass. On production hosts that support environment variables, read them with getenv() instead.
Step 3: Create a reusable connection file. In db.php, build the PDO object once:
$config = require __DIR__ . '/../config.php';
$dsn = "mysql:host={$config['host']};dbname={$config['db']};charset=utf8mb4";
$pdo = new PDO($dsn, $config['user'], $config['pass'], [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]);Step 4: Handle the form submission. In your form handler, check that the request method is POST, trim each field, validate the email with filter_var($email, FILTER_VALIDATE_EMAIL), and enforce sensible maximum lengths that match your column sizes.
Step 5: Insert with a prepared statement. Prepare INSERT INTO messages (name, email, body) VALUES (:name, :email, :body), then call execute() with an associative array of values. Redirect after a successful insert so refreshing the page does not resubmit the form.
Step 6: Read and display rows. Query the latest messages with ORDER BY created_at DESC LIMIT 20, loop over the results, and wrap every printed value in htmlspecialchars().
Step 7: Wrap the connection in try/catch. Catch PDOException, write the message to your error log with error_log(), and show a friendly fallback page.
Step 8: Deploy carefully. On your host, create the production database and user through the control panel, upload the config file above the web root, import your table structure, and test the form once with real data before sharing the link.
Key Takeaways
- PHP connects to MySQL on the server, so the browser never sees credentials or talks to the database directly.
- PDO is the most flexible default for new PHP projects, while MySQLi remains secure for MySQL-only work.
- Prepared statements with bound parameters are the primary defense against SQL injection and should wrap every query containing user input.
- A dedicated, least-privilege database user and a config file outside the web root limit the damage of any breach.
- Escaping output with htmlspecialchars() is still required, because protecting the database does not protect the page.
Frequently Asked Questions
Is PDO or MySQLi more secure?
Neither is inherently more secure; both are safe when you use prepared statements correctly. Security problems come from concatenating user input into SQL strings, not from the driver choice. PDO is often recommended because named placeholders and multi-database support make safe code easier to write and maintain.
Why does my PHP connection say access denied for user?
An access denied error usually means the username, password, or host is wrong, or the user lacks privileges on that database. Check credentials in your hosting panel, confirm the database hostname your host provides, and make sure the user has been explicitly granted access to the specific database.
Can I connect to MySQL with JavaScript instead of PHP?
Browser JavaScript should never connect to MySQL directly, because the credentials would be visible to every visitor. Server-side JavaScript running in Node.js can connect using a driver such as mysql2. The rule is the same in every language: database access happens on the server.
What is the default MySQL port?
MySQL listens on port 3306 by default. Most PHP connections on the same server do not need the port specified, because the default is assumed. If your host uses a different port or a remote database server, add port to the DSN string alongside the host and database name.
Do I need to close the PDO connection manually?
No, PHP closes the connection automatically when the script finishes running. You can set the PDO variable to null to close it earlier in long-running scripts, but for typical page requests that complete in a fraction of a second, manual closing adds nothing useful.
How do I store passwords in MySQL with PHP?
Store only a hash created with password_hash(), which uses a strong, salted algorithm by default. When users log in, compare their input with password_verify(). Use a VARCHAR(255) column so the hash fits even if PHP changes its default algorithm in future versions.
Conclusion
The decision that matters most when connecting PHP to MySQL is committing to prepared statements and least-privilege credentials before you write your first real feature. Get that foundation right and every form, product list, or login screen you add later inherits it automatically. Once real data is flowing into your database, it becomes the most valuable part of your site, so your next step should be to set up automatic free backups that copy both files and tables off the server on a schedule.




