diff --git a/api/database/database.go b/api/database/database.go
index 2e605cf..9ca0095 100644
--- a/api/database/database.go
+++ b/api/database/database.go
@@ -10,6 +10,7 @@ import (
 
 	"gorm.io/driver/mysql"
 	"gorm.io/gorm"
+	"gorm.io/gorm/logger"
 )
 
 // SetupDatabase connects to the database using environment variables
@@ -32,39 +33,17 @@ func SetupDatabase() (*gorm.DB, error) {
 
 	log.Printf("Connecting to database: %s", address)
 
-	db, err := gorm.Open(mysql.Open(address.String()), &gorm.Config{})
+	config := gorm.Config{}
+
+	// Enable database debug logging
+	config.Logger = logger.Default.LogMode(logger.Info)
+
+	db, err := gorm.Open(mysql.Open(address.String()), &config)
 	if err != nil {
 		return nil, errors.Wrap(err, "could not connect to database")
 	}
 
-	// var db *sql.DB
-
-	// db, err = sql.Open("mysql", address.String())
-	// if err != nil {
-	// 	return nil, errors.New("Could not connect to database, exiting")
-	// }
-
-	// tryCount := 0
-
-	// for {
-	// 	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
-	// 	defer cancel()
-
-	// 	if err := db.PingContext(ctx); err != nil {
-	// 		if tryCount < 4 {
-	// 			tryCount++
-	// 			log.Printf("WARN: Could not ping database: %s, Will retry after 1 second", err)
-	// 			time.Sleep(time.Second)
-	// 			continue
-	// 		} else {
-	// 			return nil, errors.Wrap(err, "Could not ping database, exiting")
-	// 		}
-	// 	}
-
-	// 	break
-	// }
-
-	// db.SetMaxOpenConns(80)
+	// TODO: Add connection retries
 
 	return db, nil
 }
diff --git a/api/graphql/models/media.go b/api/graphql/models/media.go
index f7f5cfe..aacfc11 100644
--- a/api/graphql/models/media.go
+++ b/api/graphql/models/media.go
@@ -1,11 +1,14 @@
 package models
 
 import (
+	"crypto/md5"
+	"encoding/hex"
 	"path"
 	"strings"
 	"time"
 
 	"github.com/viktorstrate/photoview/api/utils"
+	"gorm.io/gorm"
 )
 
 type Media struct {
@@ -32,6 +35,20 @@ func (Media) TableName() string {
 	return "media"
 }
 
+func (m *Media) BeforeSave(tx *gorm.DB) error {
+	// Update hashes
+	hash := md5.Sum([]byte(m.Path))
+	m.PathHash = hex.EncodeToString(hash[:])
+
+	if m.SideCarPath != nil {
+		hash = md5.Sum([]byte(*m.SideCarPath))
+		encodedHash := hex.EncodeToString(hash[:])
+		m.SideCarHash = &encodedHash
+	}
+
+	return nil
+}
+
 type MediaPurpose string
 
 const (
diff --git a/api/graphql/resolvers/album.go b/api/graphql/resolvers/album.go
index 50a02ab..600b77e 100644
--- a/api/graphql/resolvers/album.go
+++ b/api/graphql/resolvers/album.go
@@ -67,7 +67,7 @@ func (r *albumResolver) Media(ctx context.Context, album *models.Album, filter *
 	query := r.Database.
 		Joins("Album").
 		Where("Album.id = ?", album.ID).
-		Where("media.id IN (?)", r.Database.Model(&models.MediaURL{})).Select("media_id").Where("media_url.media_id = media.id")
+		Where("media.id IN (?)", r.Database.Model(&models.MediaURL{}).Select("media_urls.media_id").Where("media_urls.media_id = media.id"))
 
 	if onlyFavorites != nil && *onlyFavorites == true {
 		query = query.Where("media.favorite = 1")
diff --git a/api/graphql/resolvers/media.go b/api/graphql/resolvers/media.go
index 14d5eb0..91fff89 100644
--- a/api/graphql/resolvers/media.go
+++ b/api/graphql/resolvers/media.go
@@ -149,7 +149,7 @@ func (r *mediaResolver) Downloads(ctx context.Context, media *models.Media) ([]*
 func (r *mediaResolver) HighRes(ctx context.Context, media *models.Media) (*models.MediaURL, error) {
 	var url models.MediaURL
 	err := r.Database.
-		Where("media_url = ?", media.ID).
+		Where("media_id = ?", media.ID).
 		Where("purpose = ? OR (purpose = ? AND content_type IN ?)", models.PhotoHighRes, models.MediaOriginal, scanner.WebMimetypes).
 		First(&url).Error
 
@@ -167,7 +167,7 @@ func (r *mediaResolver) HighRes(ctx context.Context, media *models.Media) (*mode
 func (r *mediaResolver) Thumbnail(ctx context.Context, media *models.Media) (*models.MediaURL, error) {
 	var url models.MediaURL
 	err := r.Database.
-		Where("media_url = ?", media.ID).
+		Where("media_id = ?", media.ID).
 		Where("purpose = ? OR purpose = ?", models.PhotoThumbnail, models.VideoThumbnail).
 		First(&url).Error
 
@@ -182,7 +182,7 @@ func (r *mediaResolver) VideoWeb(ctx context.Context, media *models.Media) (*mod
 
 	var url models.MediaURL
 	err := r.Database.
-		Where("media_url = ?", media.ID).
+		Where("media_id = ?", media.ID).
 		Where("purpose = ?", models.VideoWeb).
 		First(&url).Error
 
diff --git a/api/scanner/cleanup_media.go b/api/scanner/cleanup_media.go
index 4a8b6b8..d47791c 100644
--- a/api/scanner/cleanup_media.go
+++ b/api/scanner/cleanup_media.go
@@ -20,14 +20,14 @@ func CleanupMedia(db *gorm.DB, albumId int, albumMedia []*models.Media) []error
 	// Will get from database
 	var mediaList []models.Media
 
-	db.Where("album_id = ?", albumId)
+	query := db.Where("album_id = ?", albumId)
 
 	// Select media from database that was not found on hard disk
 	if len(albumMedia) > 0 {
-		db.Not(albumMediaIds)
+		query.Where("NOT id IN ?", albumMediaIds)
 	}
 
-	if err := db.Find(&mediaList).Error; err != nil {
+	if err := query.Find(&mediaList).Error; err != nil {
 		return []error{errors.Wrap(err, "get media files to be deleted from database")}
 	}
 
@@ -45,8 +45,10 @@ func CleanupMedia(db *gorm.DB, albumId int, albumMedia []*models.Media) []error
 
 	}
 
-	if err := db.Where("id IN ?", mediaIDs).Delete(models.Media{}).Error; err != nil {
-		deleteErrors = append(deleteErrors, errors.Wrap(err, "delete old media from database"))
+	if len(mediaIDs) > 0 {
+		if err := db.Where("id IN ?", mediaIDs).Delete(models.Media{}).Error; err != nil {
+			deleteErrors = append(deleteErrors, errors.Wrap(err, "delete old media from database"))
+		}
 	}
 
 	return deleteErrors
diff --git a/api/scanner/process_photo.go b/api/scanner/process_photo.go
index 6e12275..d223028 100644
--- a/api/scanner/process_photo.go
+++ b/api/scanner/process_photo.go
@@ -22,19 +22,22 @@ import (
 )
 
 // Higher order function used to check if MediaURL for a given MediaPurpose exists
-func makePhotoURLChecker(tx *gorm.DB, mediaID int) (func(purpose models.MediaPurpose) (*models.MediaURL, error), error) {
+func makePhotoURLChecker(tx *gorm.DB, mediaID int) func(purpose models.MediaPurpose) (*models.MediaURL, error) {
 	return func(purpose models.MediaPurpose) (*models.MediaURL, error) {
-		var mediaURL models.MediaURL
+		var mediaURL []*models.MediaURL
 
-		if err := tx.Where("purpose = ?", purpose).First(&mediaURL, mediaID).Error; err != nil {
-			if errors.Is(err, gorm.ErrRecordNotFound) {
-				return nil, nil
-			}
-			return nil, err
+		result := tx.Where("purpose = ?", purpose).Find(&mediaURL, mediaID)
+
+		if result.Error != nil {
+			return nil, result.Error
 		}
 
-		return &mediaURL, nil
-	}, nil
+		if result.RowsAffected > 0 {
+			return mediaURL[0], nil
+		}
+
+		return nil, nil
+	}
 }
 
 func ProcessMedia(tx *gorm.DB, media *models.Media) (bool, error) {
@@ -68,25 +71,22 @@ func processPhoto(tx *gorm.DB, imageData *EncodeMediaData, photoCachePath *strin
 
 	didProcess := false
 
-	photoUrlFromDB, err := makePhotoURLChecker(tx, photo.ID)
-	if err != nil {
-		return false, err
-	}
+	photoURLFromDB := makePhotoURLChecker(tx, photo.ID)
 
 	// original photo url
-	origURL, err := photoUrlFromDB(models.MediaOriginal)
+	origURL, err := photoURLFromDB(models.MediaOriginal)
 	if err != nil {
 		return false, err
 	}
 
 	// Thumbnail
-	thumbURL, err := photoUrlFromDB(models.PhotoThumbnail)
+	thumbURL, err := photoURLFromDB(models.PhotoThumbnail)
 	if err != nil {
 		return false, errors.Wrap(err, "error processing photo thumbnail")
 	}
 
 	// Highres
-	highResURL, err := photoUrlFromDB(models.PhotoHighRes)
+	highResURL, err := photoURLFromDB(models.PhotoHighRes)
 	if err != nil {
 		return false, errors.Wrap(err, "error processing photo highres")
 	}
diff --git a/api/scanner/process_video.go b/api/scanner/process_video.go
index cf4ce3e..77809c2 100644
--- a/api/scanner/process_video.go
+++ b/api/scanner/process_video.go
@@ -22,17 +22,14 @@ func processVideo(tx *gorm.DB, mediaData *EncodeMediaData, videoCachePath *strin
 
 	log.Printf("Processing video: %s", video.Path)
 
-	mediaUrlFromDB, err := makePhotoURLChecker(tx, video.ID)
-	if err != nil {
-		return false, err
-	}
+	mediaURLFromDB := makePhotoURLChecker(tx, video.ID)
 
-	videoWebURL, err := mediaUrlFromDB(models.VideoWeb)
+	videoWebURL, err := mediaURLFromDB(models.VideoWeb)
 	if err != nil {
 		return false, errors.Wrap(err, "error processing video web-format")
 	}
 
-	videoThumbnailURL, err := mediaUrlFromDB(models.VideoThumbnail)
+	videoThumbnailURL, err := mediaURLFromDB(models.VideoThumbnail)
 	if err != nil {
 		return false, errors.Wrap(err, "error processing video thumbnail")
 	}
diff --git a/api/scanner/scanner_album.go b/api/scanner/scanner_album.go
index a150db0..15d58a9 100644
--- a/api/scanner/scanner_album.go
+++ b/api/scanner/scanner_album.go
@@ -40,7 +40,7 @@ func scanAlbum(album *models.Album, cache *AlbumScannerCache, db *gorm.DB) {
 	for count, photo := range albumPhotos {
 		// tx, err := db.Begin()
 
-		transactionResult := db.Transaction(func(tx *gorm.DB) error {
+		transactionError := db.Transaction(func(tx *gorm.DB) error {
 			processing_was_needed, err := ProcessMedia(tx, photo)
 			if err != nil {
 				return errors.Wrapf(err, "failed to process photo (%s)", photo.Path)
@@ -61,8 +61,8 @@ func scanAlbum(album *models.Album, cache *AlbumScannerCache, db *gorm.DB) {
 			return nil
 		})
 
-		if transactionResult.Error != nil {
-			ScannerError("Failed to begin database transaction: %s", transactionResult.Error)
+		if transactionError != nil {
+			ScannerError("Failed to begin database transaction: %s", transactionError)
 		}
 	}
 
diff --git a/api/scanner/scanner_media.go b/api/scanner/scanner_media.go
index 05dde2b..95a0888 100644
--- a/api/scanner/scanner_media.go
+++ b/api/scanner/scanner_media.go
@@ -52,14 +52,17 @@ func ScanMedia(tx *gorm.DB, mediaPath string, albumId int, cache *AlbumScannerCa
 
 	// Check if media already exists
 	{
-		var media models.Media
-		if err := tx.Where("path_hash = MD5(?)", mediaPath).First(&media).Error; errors.Is(err, gorm.ErrRecordNotFound) {
-			if err == nil {
-				log.Printf("Media already scanned: %s\n", mediaPath)
-				return &media, false, nil
-			} else {
-				return nil, false, errors.Wrap(err, "scan media fetch from database")
-			}
+		var media []*models.Media
+
+		result := tx.Where("path_hash = MD5(?)", mediaPath).Find(&media)
+
+		if result.Error != nil {
+			return nil, false, errors.Wrap(result.Error, "scan media fetch from database")
+		}
+
+		if result.RowsAffected > 0 {
+			log.Printf("Media already scanned: %s\n", mediaPath)
+			return media[0], false, nil
 		}
 	}
 
