diff --git a/api/database/migrations/0002_photo.down.sql b/api/database/migrations/0002_photo.down.sql
index 4c8187b..e3995cc 100644
--- a/api/database/migrations/0002_photo.down.sql
+++ b/api/database/migrations/0002_photo.down.sql
@@ -1,3 +1,4 @@
 DROP TABLE IF EXISTS photo;
 DROP TABLE IF EXISTS album;
 DROP TABLE IF EXISTS photo_url;
+DROP TABLE IF EXISTS photo_exif;
diff --git a/api/database/migrations/0002_photo.up.sql b/api/database/migrations/0002_photo.up.sql
index 0ef424b..4b4046f 100644
--- a/api/database/migrations/0002_photo.up.sql
+++ b/api/database/migrations/0002_photo.up.sql
@@ -1,12 +1,28 @@
 CREATE TABLE IF NOT EXISTS photo_url (
   url_id int NOT NULL AUTO_INCREMENT,
-  path varchar(256) NUT NULL,
+  token varchar(256) NOT NULL,
   width int NOT NULL,
   height int NOT NULL,
 
   PRIMARY KEY (url_id)
 );
 
+CREATE TABLE IF NOT EXISTS photo_exif (
+  exif_id int NOT NULL AUTO_INCREMENT,
+  camera varchar(256),
+  maker varchar(256),
+  lens varchar(256),
+  dateShot timestamp,
+  file_size_bytes bigint,
+  exposure varchar(256),
+  aperature float,
+  iso int(6),
+  focal_length float,
+  flash varchar(256),
+
+  PRIMARY KEY (exif_id)
+);
+
 CREATE TABLE IF NOT EXISTS album (
   album_id int NOT NULL AUTO_INCREMENT,
   title varchar(256) NOT NULL,
@@ -23,12 +39,14 @@ CREATE TABLE IF NOT EXISTS photo (
   photo_id int NOT NULL AUTO_INCREMENT,
   title varchar(256) NOT NULL,
   path varchar(512) NOT NULL UNIQUE,
-  -- original_url int NOT NULL,
-  -- thumbnail_url int NOT NULL,
+  original_url int NOT NULL,
+  thumbnail_url int NOT NULL,
   album_id int NOT NULL,
-  -- exif_id int NOT NULL,
+  exif_id int,
 
   PRIMARY KEY (photo_id),
+  FOREIGN KEY (album_id) REFERENCES album(album_id),
+  FOREIGN KEY (exif_id) REFERENCES photo_exif(exif_id),
   FOREIGN KEY (original_url) REFERENCES photo_url(url_id),
   FOREIGN KEY (thumbnail_url) REFERENCES photo_url(url_id)
-);
\ No newline at end of file
+);
diff --git a/api/graphql/generated.go b/api/graphql/generated.go
index eaa5bc1..216fae0 100644
--- a/api/graphql/generated.go
+++ b/api/graphql/generated.go
@@ -10,6 +10,7 @@ import (
 	"strconv"
 	"sync"
 	"sync/atomic"
+	"time"
 
 	"github.com/99designs/gqlgen/graphql"
 	"github.com/99designs/gqlgen/graphql/introspection"
@@ -70,6 +71,7 @@ type ComplexityRoot struct {
 
 	Photo struct {
 		Album     func(childComplexity int) int
+		Exif      func(childComplexity int) int
 		ID        func(childComplexity int) int
 		Original  func(childComplexity int) int
 		Path      func(childComplexity int) int
@@ -77,6 +79,20 @@ type ComplexityRoot struct {
 		Title     func(childComplexity int) int
 	}
 
+	PhotoExif struct {
+		Aperture    func(childComplexity int) int
+		Camera      func(childComplexity int) int
+		DateShot    func(childComplexity int) int
+		Exposure    func(childComplexity int) int
+		FileSize    func(childComplexity int) int
+		Flash       func(childComplexity int) int
+		FocalLength func(childComplexity int) int
+		Iso         func(childComplexity int) int
+		Lens        func(childComplexity int) int
+		Maker       func(childComplexity int) int
+		Photo       func(childComplexity int) int
+	}
+
 	PhotoURL struct {
 		Height func(childComplexity int) int
 		URL    func(childComplexity int) int
@@ -257,6 +273,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
 
 		return e.complexity.Photo.Album(childComplexity), true
 
+	case "Photo.exif":
+		if e.complexity.Photo.Exif == nil {
+			break
+		}
+
+		return e.complexity.Photo.Exif(childComplexity), true
+
 	case "Photo.id":
 		if e.complexity.Photo.ID == nil {
 			break
@@ -292,6 +315,83 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
 
 		return e.complexity.Photo.Title(childComplexity), true
 
+	case "PhotoEXIF.aperture":
+		if e.complexity.PhotoExif.Aperture == nil {
+			break
+		}
+
+		return e.complexity.PhotoExif.Aperture(childComplexity), true
+
+	case "PhotoEXIF.camera":
+		if e.complexity.PhotoExif.Camera == nil {
+			break
+		}
+
+		return e.complexity.PhotoExif.Camera(childComplexity), true
+
+	case "PhotoEXIF.dateShot":
+		if e.complexity.PhotoExif.DateShot == nil {
+			break
+		}
+
+		return e.complexity.PhotoExif.DateShot(childComplexity), true
+
+	case "PhotoEXIF.exposure":
+		if e.complexity.PhotoExif.Exposure == nil {
+			break
+		}
+
+		return e.complexity.PhotoExif.Exposure(childComplexity), true
+
+	case "PhotoEXIF.fileSize":
+		if e.complexity.PhotoExif.FileSize == nil {
+			break
+		}
+
+		return e.complexity.PhotoExif.FileSize(childComplexity), true
+
+	case "PhotoEXIF.flash":
+		if e.complexity.PhotoExif.Flash == nil {
+			break
+		}
+
+		return e.complexity.PhotoExif.Flash(childComplexity), true
+
+	case "PhotoEXIF.focalLength":
+		if e.complexity.PhotoExif.FocalLength == nil {
+			break
+		}
+
+		return e.complexity.PhotoExif.FocalLength(childComplexity), true
+
+	case "PhotoEXIF.iso":
+		if e.complexity.PhotoExif.Iso == nil {
+			break
+		}
+
+		return e.complexity.PhotoExif.Iso(childComplexity), true
+
+	case "PhotoEXIF.lens":
+		if e.complexity.PhotoExif.Lens == nil {
+			break
+		}
+
+		return e.complexity.PhotoExif.Lens(childComplexity), true
+
+	case "PhotoEXIF.maker":
+		if e.complexity.PhotoExif.Maker == nil {
+			break
+		}
+
+		return e.complexity.PhotoExif.Maker(childComplexity), true
+
+	case "PhotoEXIF.photo":
+		if e.complexity.PhotoExif.Photo == nil {
+			break
+		}
+
+		return e.complexity.PhotoExif.Photo(childComplexity), true
+
 	case "PhotoURL.height":
 		if e.complexity.PhotoURL.Height == nil {
 			break
@@ -574,11 +674,35 @@ type Photo {
   thumbnail: PhotoURL
   "The album that holds the photo"
   album: Album!
-  # exif: PhotoEXIF
+  exif: PhotoEXIF
 
   # shares: [ShareToken]
   # downloads: [PhotoDownload]
 }
+
+"EXIF metadata from the camera"
+type PhotoEXIF {
+  photo: Photo
+  "The model name of the camera"
+  camera: String
+  "The maker of the camera"
+  maker: String
+  "The name of the lens"
+  lens: String
+  dateShot: Time
+  "The formatted filesize of the image"
+  fileSize: String
+  "The exposure time of the image"
+  exposure: String
+  "The aperature stops of the image"
+  aperture: Float
+  "The ISO setting of the image"
+  iso: Int
+  "The focal length of the lens, when the image was taken"
+  focalLength: String
+  "A formatted description of the flash settings, when the image was taken"
+  flash: String
+}
 `},
 )
 
@@ -1461,6 +1585,414 @@ func (ec *executionContext) _Photo_album(ctx context.Context, field graphql.Coll
 	return ec.marshalNAlbum2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐAlbum(ctx, field.Selections, res)
 }
 
+func (ec *executionContext) _Photo_exif(ctx context.Context, field graphql.CollectedField, obj *models.Photo) (ret graphql.Marshaler) {
+	ctx = ec.Tracer.StartFieldExecution(ctx, field)
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+		ec.Tracer.EndFieldExecution(ctx)
+	}()
+	rctx := &graphql.ResolverContext{
+		Object:   "Photo",
+		Field:    field,
+		Args:     nil,
+		IsMethod: false,
+	}
+	ctx = graphql.WithResolverContext(ctx, rctx)
+	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return obj.Exif, nil
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.(*models.PhotoExif)
+	rctx.Result = res
+	ctx = ec.Tracer.StartFieldChildExecution(ctx)
+	return ec.marshalOPhotoEXIF2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐPhotoExif(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) _PhotoEXIF_photo(ctx context.Context, field graphql.CollectedField, obj *models.PhotoExif) (ret graphql.Marshaler) {
+	ctx = ec.Tracer.StartFieldExecution(ctx, field)
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+		ec.Tracer.EndFieldExecution(ctx)
+	}()
+	rctx := &graphql.ResolverContext{
+		Object:   "PhotoEXIF",
+		Field:    field,
+		Args:     nil,
+		IsMethod: false,
+	}
+	ctx = graphql.WithResolverContext(ctx, rctx)
+	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return obj.Photo, nil
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.(*models.Photo)
+	rctx.Result = res
+	ctx = ec.Tracer.StartFieldChildExecution(ctx)
+	return ec.marshalOPhoto2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐPhoto(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) _PhotoEXIF_camera(ctx context.Context, field graphql.CollectedField, obj *models.PhotoExif) (ret graphql.Marshaler) {
+	ctx = ec.Tracer.StartFieldExecution(ctx, field)
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+		ec.Tracer.EndFieldExecution(ctx)
+	}()
+	rctx := &graphql.ResolverContext{
+		Object:   "PhotoEXIF",
+		Field:    field,
+		Args:     nil,
+		IsMethod: false,
+	}
+	ctx = graphql.WithResolverContext(ctx, rctx)
+	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return obj.Camera, nil
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.(*string)
+	rctx.Result = res
+	ctx = ec.Tracer.StartFieldChildExecution(ctx)
+	return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) _PhotoEXIF_maker(ctx context.Context, field graphql.CollectedField, obj *models.PhotoExif) (ret graphql.Marshaler) {
+	ctx = ec.Tracer.StartFieldExecution(ctx, field)
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+		ec.Tracer.EndFieldExecution(ctx)
+	}()
+	rctx := &graphql.ResolverContext{
+		Object:   "PhotoEXIF",
+		Field:    field,
+		Args:     nil,
+		IsMethod: false,
+	}
+	ctx = graphql.WithResolverContext(ctx, rctx)
+	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return obj.Maker, nil
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.(*string)
+	rctx.Result = res
+	ctx = ec.Tracer.StartFieldChildExecution(ctx)
+	return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) _PhotoEXIF_lens(ctx context.Context, field graphql.CollectedField, obj *models.PhotoExif) (ret graphql.Marshaler) {
+	ctx = ec.Tracer.StartFieldExecution(ctx, field)
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+		ec.Tracer.EndFieldExecution(ctx)
+	}()
+	rctx := &graphql.ResolverContext{
+		Object:   "PhotoEXIF",
+		Field:    field,
+		Args:     nil,
+		IsMethod: false,
+	}
+	ctx = graphql.WithResolverContext(ctx, rctx)
+	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return obj.Lens, nil
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.(*string)
+	rctx.Result = res
+	ctx = ec.Tracer.StartFieldChildExecution(ctx)
+	return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) _PhotoEXIF_dateShot(ctx context.Context, field graphql.CollectedField, obj *models.PhotoExif) (ret graphql.Marshaler) {
+	ctx = ec.Tracer.StartFieldExecution(ctx, field)
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+		ec.Tracer.EndFieldExecution(ctx)
+	}()
+	rctx := &graphql.ResolverContext{
+		Object:   "PhotoEXIF",
+		Field:    field,
+		Args:     nil,
+		IsMethod: false,
+	}
+	ctx = graphql.WithResolverContext(ctx, rctx)
+	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return obj.DateShot, nil
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.(*time.Time)
+	rctx.Result = res
+	ctx = ec.Tracer.StartFieldChildExecution(ctx)
+	return ec.marshalOTime2ᚖtimeᚐTime(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) _PhotoEXIF_fileSize(ctx context.Context, field graphql.CollectedField, obj *models.PhotoExif) (ret graphql.Marshaler) {
+	ctx = ec.Tracer.StartFieldExecution(ctx, field)
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+		ec.Tracer.EndFieldExecution(ctx)
+	}()
+	rctx := &graphql.ResolverContext{
+		Object:   "PhotoEXIF",
+		Field:    field,
+		Args:     nil,
+		IsMethod: false,
+	}
+	ctx = graphql.WithResolverContext(ctx, rctx)
+	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return obj.FileSize, nil
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.(*string)
+	rctx.Result = res
+	ctx = ec.Tracer.StartFieldChildExecution(ctx)
+	return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) _PhotoEXIF_exposure(ctx context.Context, field graphql.CollectedField, obj *models.PhotoExif) (ret graphql.Marshaler) {
+	ctx = ec.Tracer.StartFieldExecution(ctx, field)
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+		ec.Tracer.EndFieldExecution(ctx)
+	}()
+	rctx := &graphql.ResolverContext{
+		Object:   "PhotoEXIF",
+		Field:    field,
+		Args:     nil,
+		IsMethod: false,
+	}
+	ctx = graphql.WithResolverContext(ctx, rctx)
+	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return obj.Exposure, nil
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.(*string)
+	rctx.Result = res
+	ctx = ec.Tracer.StartFieldChildExecution(ctx)
+	return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) _PhotoEXIF_aperture(ctx context.Context, field graphql.CollectedField, obj *models.PhotoExif) (ret graphql.Marshaler) {
+	ctx = ec.Tracer.StartFieldExecution(ctx, field)
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+		ec.Tracer.EndFieldExecution(ctx)
+	}()
+	rctx := &graphql.ResolverContext{
+		Object:   "PhotoEXIF",
+		Field:    field,
+		Args:     nil,
+		IsMethod: false,
+	}
+	ctx = graphql.WithResolverContext(ctx, rctx)
+	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return obj.Aperture, nil
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.(*float64)
+	rctx.Result = res
+	ctx = ec.Tracer.StartFieldChildExecution(ctx)
+	return ec.marshalOFloat2ᚖfloat64(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) _PhotoEXIF_iso(ctx context.Context, field graphql.CollectedField, obj *models.PhotoExif) (ret graphql.Marshaler) {
+	ctx = ec.Tracer.StartFieldExecution(ctx, field)
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+		ec.Tracer.EndFieldExecution(ctx)
+	}()
+	rctx := &graphql.ResolverContext{
+		Object:   "PhotoEXIF",
+		Field:    field,
+		Args:     nil,
+		IsMethod: false,
+	}
+	ctx = graphql.WithResolverContext(ctx, rctx)
+	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return obj.Iso, nil
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.(*int)
+	rctx.Result = res
+	ctx = ec.Tracer.StartFieldChildExecution(ctx)
+	return ec.marshalOInt2ᚖint(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) _PhotoEXIF_focalLength(ctx context.Context, field graphql.CollectedField, obj *models.PhotoExif) (ret graphql.Marshaler) {
+	ctx = ec.Tracer.StartFieldExecution(ctx, field)
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+		ec.Tracer.EndFieldExecution(ctx)
+	}()
+	rctx := &graphql.ResolverContext{
+		Object:   "PhotoEXIF",
+		Field:    field,
+		Args:     nil,
+		IsMethod: false,
+	}
+	ctx = graphql.WithResolverContext(ctx, rctx)
+	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return obj.FocalLength, nil
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.(*string)
+	rctx.Result = res
+	ctx = ec.Tracer.StartFieldChildExecution(ctx)
+	return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) _PhotoEXIF_flash(ctx context.Context, field graphql.CollectedField, obj *models.PhotoExif) (ret graphql.Marshaler) {
+	ctx = ec.Tracer.StartFieldExecution(ctx, field)
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+		ec.Tracer.EndFieldExecution(ctx)
+	}()
+	rctx := &graphql.ResolverContext{
+		Object:   "PhotoEXIF",
+		Field:    field,
+		Args:     nil,
+		IsMethod: false,
+	}
+	ctx = graphql.WithResolverContext(ctx, rctx)
+	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return obj.Flash, nil
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.(*string)
+	rctx.Result = res
+	ctx = ec.Tracer.StartFieldChildExecution(ctx)
+	return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
+}
+
 func (ec *executionContext) _PhotoURL_url(ctx context.Context, field graphql.CollectedField, obj *models.PhotoURL) (ret graphql.Marshaler) {
 	ctx = ec.Tracer.StartFieldExecution(ctx, field)
 	defer func() {
@@ -3499,6 +4031,52 @@ func (ec *executionContext) _Photo(ctx context.Context, sel ast.SelectionSet, ob
 			if out.Values[i] == graphql.Null {
 				invalids++
 			}
+		case "exif":
+			out.Values[i] = ec._Photo_exif(ctx, field, obj)
+		default:
+			panic("unknown field " + strconv.Quote(field.Name))
+		}
+	}
+	out.Dispatch()
+	if invalids > 0 {
+		return graphql.Null
+	}
+	return out
+}
+
+var photoEXIFImplementors = []string{"PhotoEXIF"}
+
+func (ec *executionContext) _PhotoEXIF(ctx context.Context, sel ast.SelectionSet, obj *models.PhotoExif) graphql.Marshaler {
+	fields := graphql.CollectFields(ec.RequestContext, sel, photoEXIFImplementors)
+
+	out := graphql.NewFieldSet(fields)
+	var invalids uint32
+	for i, field := range fields {
+		switch field.Name {
+		case "__typename":
+			out.Values[i] = graphql.MarshalString("PhotoEXIF")
+		case "photo":
+			out.Values[i] = ec._PhotoEXIF_photo(ctx, field, obj)
+		case "camera":
+			out.Values[i] = ec._PhotoEXIF_camera(ctx, field, obj)
+		case "maker":
+			out.Values[i] = ec._PhotoEXIF_maker(ctx, field, obj)
+		case "lens":
+			out.Values[i] = ec._PhotoEXIF_lens(ctx, field, obj)
+		case "dateShot":
+			out.Values[i] = ec._PhotoEXIF_dateShot(ctx, field, obj)
+		case "fileSize":
+			out.Values[i] = ec._PhotoEXIF_fileSize(ctx, field, obj)
+		case "exposure":
+			out.Values[i] = ec._PhotoEXIF_exposure(ctx, field, obj)
+		case "aperture":
+			out.Values[i] = ec._PhotoEXIF_aperture(ctx, field, obj)
+		case "iso":
+			out.Values[i] = ec._PhotoEXIF_iso(ctx, field, obj)
+		case "focalLength":
+			out.Values[i] = ec._PhotoEXIF_focalLength(ctx, field, obj)
+		case "flash":
+			out.Values[i] = ec._PhotoEXIF_flash(ctx, field, obj)
 		default:
 			panic("unknown field " + strconv.Quote(field.Name))
 		}
@@ -4515,6 +5093,17 @@ func (ec *executionContext) marshalOPhoto2ᚖgithubᚗcomᚋviktorstrateᚋphoto
 	return ec._Photo(ctx, sel, v)
 }
 
+func (ec *executionContext) marshalOPhotoEXIF2githubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐPhotoExif(ctx context.Context, sel ast.SelectionSet, v models.PhotoExif) graphql.Marshaler {
+	return ec._PhotoEXIF(ctx, sel, &v)
+}
+
+func (ec *executionContext) marshalOPhotoEXIF2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐPhotoExif(ctx context.Context, sel ast.SelectionSet, v *models.PhotoExif) graphql.Marshaler {
+	if v == nil {
+		return graphql.Null
+	}
+	return ec._PhotoEXIF(ctx, sel, v)
+}
+
 func (ec *executionContext) marshalOPhotoURL2githubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐPhotoURL(ctx context.Context, sel ast.SelectionSet, v models.PhotoURL) graphql.Marshaler {
 	return ec._PhotoURL(ctx, sel, &v)
 }
@@ -4549,6 +5138,29 @@ func (ec *executionContext) marshalOString2ᚖstring(ctx context.Context, sel as
 	return ec.marshalOString2string(ctx, sel, *v)
 }
 
+func (ec *executionContext) unmarshalOTime2timeᚐTime(ctx context.Context, v interface{}) (time.Time, error) {
+	return graphql.UnmarshalTime(v)
+}
+
+func (ec *executionContext) marshalOTime2timeᚐTime(ctx context.Context, sel ast.SelectionSet, v time.Time) graphql.Marshaler {
+	return graphql.MarshalTime(v)
+}
+
+func (ec *executionContext) unmarshalOTime2ᚖtimeᚐTime(ctx context.Context, v interface{}) (*time.Time, error) {
+	if v == nil {
+		return nil, nil
+	}
+	res, err := ec.unmarshalOTime2timeᚐTime(ctx, v)
+	return &res, err
+}
+
+func (ec *executionContext) marshalOTime2ᚖtimeᚐTime(ctx context.Context, sel ast.SelectionSet, v *time.Time) graphql.Marshaler {
+	if v == nil {
+		return graphql.Null
+	}
+	return ec.marshalOTime2timeᚐTime(ctx, sel, *v)
+}
+
 func (ec *executionContext) marshalOUser2githubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐUser(ctx context.Context, sel ast.SelectionSet, v models.User) graphql.Marshaler {
 	return ec._User(ctx, sel, &v)
 }
diff --git a/api/graphql/models/generated.go b/api/graphql/models/generated.go
index ebba883..8a4db55 100644
--- a/api/graphql/models/generated.go
+++ b/api/graphql/models/generated.go
@@ -2,6 +2,10 @@
 
 package models
 
+import (
+	"time"
+)
+
 type Album struct {
 	ID          string   `json:"id"`
 	Title       *string  `json:"title"`
@@ -28,7 +32,32 @@ type Photo struct {
 	// URL to display the photo in a smaller resolution
 	Thumbnail *PhotoURL `json:"thumbnail"`
 	// The album that holds the photo
-	Album *Album `json:"album"`
+	Album *Album     `json:"album"`
+	Exif  *PhotoExif `json:"exif"`
+}
+
+// EXIF metadata from the camera
+type PhotoExif struct {
+	Photo *Photo `json:"photo"`
+	// The model name of the camera
+	Camera *string `json:"camera"`
+	// The maker of the camera
+	Maker *string `json:"maker"`
+	// The name of the lens
+	Lens     *string    `json:"lens"`
+	DateShot *time.Time `json:"dateShot"`
+	// The formatted filesize of the image
+	FileSize *string `json:"fileSize"`
+	// The exposure time of the image
+	Exposure *string `json:"exposure"`
+	// The aperature stops of the image
+	Aperture *float64 `json:"aperture"`
+	// The ISO setting of the image
+	Iso *int `json:"iso"`
+	// The focal length of the lens, when the image was taken
+	FocalLength *string `json:"focalLength"`
+	// A formatted description of the flash settings, when the image was taken
+	Flash *string `json:"flash"`
 }
 
 type PhotoURL struct {
diff --git a/api/graphql/models/user.go b/api/graphql/models/user.go
index 9f1114d..3ff58ae 100644
--- a/api/graphql/models/user.go
+++ b/api/graphql/models/user.go
@@ -35,11 +35,7 @@ func NewUserFromRow(row *sql.Row) (*User, error) {
 	user := User{}
 
 	if err := row.Scan(&user.UserID, &user.Username, &user.Password, &user.RootPath, &user.Admin); err != nil {
-		if err == sql.ErrNoRows {
-			return nil, ErrorInvalidUserCredentials
-		} else {
-			return nil, err
-		}
+		return nil, err
 	}
 
 	return &user, nil
@@ -64,7 +60,11 @@ func AuthorizeUser(database *sql.DB, username string, password string) (*User, e
 
 	user, err := NewUserFromRow(row)
 	if err != nil {
-		return nil, err
+		if err == sql.ErrNoRows {
+			return nil, ErrorInvalidUserCredentials
+		} else {
+			return nil, err
+		}
 	}
 
 	if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil {
@@ -107,7 +107,7 @@ func (user *User) GenerateAccessToken(database *sql.DB) (*AccessToken, error) {
 	if _, err := rand.Read(bytes); err != nil {
 		return nil, errors.New(fmt.Sprintf("Could not generate token: %s\n", err.Error()))
 	}
-	const CHARACTERS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-"
+	const CHARACTERS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
 	for i, b := range bytes {
 		bytes[i] = CHARACTERS[b%byte(len(CHARACTERS))]
 	}
diff --git a/api/graphql/schema.graphql b/api/graphql/schema.graphql
index 86abc93..1814084 100644
--- a/api/graphql/schema.graphql
+++ b/api/graphql/schema.graphql
@@ -89,8 +89,32 @@ type Photo {
   thumbnail: PhotoURL
   "The album that holds the photo"
   album: Album!
-  # exif: PhotoEXIF
+  exif: PhotoEXIF
 
   # shares: [ShareToken]
   # downloads: [PhotoDownload]
 }
+
+"EXIF metadata from the camera"
+type PhotoEXIF {
+  photo: Photo
+  "The model name of the camera"
+  camera: String
+  "The maker of the camera"
+  maker: String
+  "The name of the lens"
+  lens: String
+  dateShot: Time
+  "The formatted filesize of the image"
+  fileSize: String
+  "The exposure time of the image"
+  exposure: String
+  "The aperature stops of the image"
+  aperture: Float
+  "The ISO setting of the image"
+  iso: Int
+  "The focal length of the lens, when the image was taken"
+  focalLength: String
+  "A formatted description of the flash settings, when the image was taken"
+  flash: String
+}
diff --git a/api/scanner/process_image.go b/api/scanner/process_image.go
new file mode 100644
index 0000000..ee024c5
--- /dev/null
+++ b/api/scanner/process_image.go
@@ -0,0 +1,15 @@
+package scanner
+
+import (
+	"database/sql"
+	"path"
+)
+
+func ProcessImage(tx *sql.Tx, photoPath string, albumId int) error {
+	photoName := path.Base(photoPath)
+
+	_, err := tx.Exec("INSERT IGNORE INTO photo (title, path, album_id) VALUES (?, ?, ?)", photoName, photoPath, albumId)
+	if err != nil {
+		return err
+	}
+}
diff --git a/api/scanner/scanner.go b/api/scanner/scanner.go
index b2189c4..184b0d6 100644
--- a/api/scanner/scanner.go
+++ b/api/scanner/scanner.go
@@ -84,7 +84,11 @@ func scan(database *sql.DB, user *models.User) {
 			photoPath := path.Join(albumPath, item.Name())
 
 			if !item.IsDir() && isPathImage(photoPath) {
-				tx.Exec("INSERT IGNORE INTO photo (title, path, original_url, thumbnail_url, album_id)")
+				if err := ProcessImage(tx, photoPath, albumId); err != nil {
+					fmt.Printf("ERROR: Could not proccess image %s: %s", photoPath, err)
+					tx.Rollback()
+					return
+				}
 			}
 		}
 
@@ -144,6 +148,7 @@ func directoryContainsPhotos(rootPath string) bool {
 var supported_mimetypes = [...]string{
 	"image/jpeg",
 	"image/png",
+	"image/tiff",
 	"image/x-canon-cr2",
 	"image/bmp",
 }
