Connecting from Application Code

Every lesson so far has run SQL directly against StreamHub's tables. In a real project, that SQL almost always gets sent by application code written in some other language - PHP, Python, or whatever a particular site is built with. This lesson closes that gap with real, working connection code in the two languages this site's own tracks already teach, and ties every example straight back to the parameterized-query fix from Security Best Practices.

1. PHP: Connecting with mysqli

PHP's mysqli extension is the same one this site's own connection.php uses. Opening a connection to StreamHub looks like this:

<?php
  $mysqli = new mysqli('localhost', 'streamhub_app', 'password', 'streamhub');

  if ($mysqli->connect_error) {
    die('Connection failed: ' . $mysqli->connect_error);
  }

Notice the account here is streamhub_app, not root - the same least-privilege account created back in Security Best Practices. If you've worked through the PHP track already, this same pattern of reading server details into variables first shows up in PHP Variables and PHP Superglobals.

2. PHP: Parameterized Queries with mysqli

Here's exactly what "the fix" from the previous lesson looks like in real code. Instead of concatenating a year straight into a query string, a placeholder (?) marks where the value belongs, and bind_param() supplies it separately:

$year = $_GET['year'] ?? '2000';

$stmt = $mysqli->prepare('SELECT * FROM movies WHERE release_year > ?');
$stmt->bind_param('i', $year);
$stmt->execute();

$result = $stmt->get_result();
while ($movie = $result->fetch_assoc()) {
  echo $movie['title'];
}

$stmt->close();
$mysqli->close();

Whatever value $year ends up holding, it is only ever treated as data being compared against release_year - it can never reshape the query itself. That's the entire idea behind the "bad concatenation vs. parameterized query" comparison from the previous lesson, written out in actual PHP.

3. PHP: PDO as an Alternative

mysqli isn't PHP's only option - PDO (PHP Data Objects) is a more general database access layer that works against MySQL and several other database systems through the same interface. It offers roughly the same parameterized-query safety, just with named placeholders instead of ? and bind_param():

$pdo = new PDO(
  'mysql:host=localhost;dbname=streamhub;charset=utf8mb4',
  'streamhub_app',
  'password'
);

$stmt = $pdo->prepare('SELECT * FROM movies WHERE release_year > :year');
$stmt->execute(['year' => $year]);
$movies = $stmt->fetchAll();

Either extension is a genuinely safe choice as long as you always reach for the prepared statement form shown above, and never build the SQL string by hand out of user input.

4. Python: Connecting with mysql-connector-python

On the Python side, mysql-connector-python (and the similar PyMySQL package) follow the exact same shape - connect, then run a parameterized query with %s placeholders instead of building a string with an f-string or +:

import mysql.connector

conn = mysql.connector.connect(
    host='localhost',
    user='streamhub_app',
    password='password',
    database='streamhub'
)

cursor = conn.cursor(dictionary=True)
year = 2015

cursor.execute("SELECT * FROM movies WHERE release_year > %s", (year,))
for movie in cursor.fetchall():
    print(movie['title'])

cursor.close()
conn.close()

The (year,) tuple passed as the second argument is what keeps this safe - the driver sends the query text and the value separately, the same guarantee bind_param() and PDO's execute() gave the PHP examples above.

5. ORMs Handle This Automatically

If you've been through this site's Django or Flask tracks, you've likely never written a raw parameterized query like the one above at all - and that's by design. Django's ORM and Flask-SQLAlchemy both generate parameterized SQL for you behind the scenes:

Movie.objects.filter(release_year__gt=2015)
Movie.query.filter(Movie.release_year > 2015).all()

Neither line touches a SQL string at all, yet both compile down to a real, safely parameterized query under the hood. An ORM isn't a different, magical way of avoiding SQL injection - it's a more convenient layer built directly on top of the exact same parameterized-query safety shown in the raw PHP and Python examples above.

6. Connection Pooling

Opening a brand-new connection to MySQL for every single incoming request is genuinely wasteful - each connection involves a real network handshake and authentication step, and doing that fresh for every request adds noticeable, unnecessary latency under real traffic. Connection pooling solves this by keeping a small set of already-open connections ready to be reused, so a request borrows one, uses it briefly, and returns it rather than tearing it down and rebuilding it from scratch. Most real frameworks - including Django's and Flask-SQLAlchemy's database layers - manage a connection pool for you automatically, so this is mostly something to be aware of rather than something you'll hand-roll yourself.

7. Closing Connections Properly

The raw examples above call close() once they're done with a connection, which is good practice when you're managing connections by hand - it frees up the resource promptly instead of leaving it held open until the script ends. In practice, frameworks and ORMs typically manage this entire lifecycle for you: a request-scoped session or connection gets opened, used, and cleaned up automatically as part of the request cycle, without you needing to remember to close anything yourself.

Note: As with every code sample in this track, the PHP and Python above is correct and complete, but static - this website's own project doesn't run a live MySQL server, so nothing here executes on the page itself.
Last stop: Best Practices pulls everything from this entire 33-lesson track together into one final checklist.