diff --git a/app/Controllers/Http/v1/LocationController.ts b/app/Controllers/Http/v1/LocationController.ts
new file mode 100644
index 00000000..b4225f51
--- /dev/null
+++ b/app/Controllers/Http/v1/LocationController.ts
@@ -0,0 +1,80 @@
+import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
+import { ApiDocument } from 'App/Helpers/Api/Document';
+import Location, { LocationStatus } from 'App/Models/Location';
+import LocationManager from 'App/Helpers/Managers/LocationManager';
+
+// TODO(matthiasrohmer): Add permissions
+export default class LocationController {
+  public async index(ctx: HttpContextContract) {
+    const manager: LocationManager = new LocationManager(ctx);
+    await manager.all();
+
+    return new ApiDocument(ctx, manager.toResources(), {
+      paginator: manager.paginator,
+    });
+  }
+
+  public async store(ctx: HttpContextContract) {
+    const manager: LocationManager = new LocationManager(ctx);
+    await manager.create();
+
+    return new ApiDocument(ctx, manager.toResources());
+  }
+
+  public async show(ctx: HttpContextContract) {
+    const manager: LocationManager = new LocationManager(ctx);
+
+    manager.include = 'address,types,subjects';
+    await manager.byId();
+
+    const location: Location = manager.instance;
+    const publishable = await location.publishable();
+
+    return new ApiDocument(ctx, manager.toResources(), { publishable });
+  }
+
+  public async update(ctx: HttpContextContract) {
+    const manager: LocationManager = new LocationManager(ctx);
+    await manager.update();
+
+    manager.include = 'address,types,subjects';
+    const location: Location = await manager.byId();
+    const publishable = await location.publishable();
+
+    if (publishable !== true) {
+      location.status = LocationStatus.DRAFT;
+      if (location.$isDirty) {
+        await location.save();
+      }
+    }
+
+    return new ApiDocument(ctx, manager.toResources(), {
+      publishable,
+    });
+  }
+
+  public async translate(ctx: HttpContextContract) {
+    const manager: LocationManager = new LocationManager(ctx);
+    await manager.byId();
+    await manager.translate();
+
+    return new ApiDocument(ctx, manager.toResources());
+  }
+
+  // public async destroy(ctx: HttpContextContract) {
+  //   const { params, auth } = ctx;
+  //   if (!auth.user) {
+  //     throw new UnauthorizedException();
+  //   }
+
+  //   const location = await Location.query()
+  //     .preload('address')
+  //     .where('cid', params.id)
+  //     .firstOrFail();
+  //   const address = location.address;
+
+  //   await Promise.all([location.delete(), address.delete()]);
+
+  //   return new ApiDocument(ctx, {}, 'Location deleted successfully');
+  // }
+}
diff --git a/app/Helpers/Managers/BaseManager.ts b/app/Helpers/Managers/BaseManager.ts
index 819db6ef..85f9439c 100644
--- a/app/Helpers/Managers/BaseManager.ts
+++ b/app/Helpers/Managers/BaseManager.ts
@@ -1,8 +1,12 @@
 import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
-import { LucidModel } from '@ioc:Adonis/Lucid/Model';
 import Resource from 'App/Helpers/Api/Resource';
-import { LucidRow, ModelPaginatorContract } from '@ioc:Adonis/Lucid/Model';
+import {
+  LucidModel,
+  LucidRow,
+  ModelPaginatorContract,
+} from '@ioc:Adonis/Lucid/Model';
 import { RawBuilderContract } from '@ioc:Adonis/Lucid/DatabaseQueryBuilder';
+import { ModelQueryBuilderContract } from '@ioc:Adonis/Lucid/Orm';
 
 interface OrderableInstruction {
   name: string;
@@ -31,14 +35,10 @@ interface ManagerSettings {
   filters?: Filter[];
 }
 
-type Model<T extends LucidModel> = T;
+export class BaseManager<ManagedModel extends LucidModel> {
+  public ManagedModel: ManagedModel;
 
-export class BaseManager {
-  public ModelClass: Model<LucidModel>;
-
-  public RessourceClass;
-
-  public instances: Array<LucidRow> = [];
+  public instances: Array<InstanceType<ManagedModel>> = [];
 
   public paginator: ModelPaginatorContract<LucidRow>;
 
@@ -60,13 +60,8 @@ export class BaseManager {
     queryId: 'id',
   };
 
-  constructor(
-    ctx: HttpContextContract,
-    ModelClass: LucidModel,
-    ResourceClass = Resource
-  ) {
-    this.ModelClass = ModelClass;
-    this.RessourceClass = ResourceClass;
+  constructor(ctx: HttpContextContract, ManagedModel: ManagedModel) {
+    this.ManagedModel = ManagedModel;
 
     this.ctx = ctx;
     this.language = ctx.language as string;
@@ -75,9 +70,9 @@ export class BaseManager {
 
   public query(
     options: { sort?: string; includes?: string; filter?: string } = {}
-  ) {
-    let query = this.ModelClass.query();
-    if (this.ModelClass.$hasRelation('translations')) {
+  ): ModelQueryBuilderContract<ManagedModel> {
+    let query = this.ManagedModel.query() as any;
+    if (this.ManagedModel.$hasRelation('translations')) {
       query = query.preload('translations');
     }
 
@@ -196,7 +191,7 @@ export class BaseManager {
     this.paginator = result;
     this.paginator.baseUrl(this.ctx.request.completeUrl());
 
-    this.instances = result.rows;
+    this.instances = result.all() as InstanceType<ManagedModel>[];
 
     return result;
   }
@@ -230,16 +225,16 @@ export class BaseManager {
     return this.instances[0];
   }
 
-  public set instance(instance) {
+  public set instance(instance: InstanceType<ManagedModel>) {
     this.instances = [instance];
   }
 
   public async create() {
-    return [new this.ModelClass()];
+    return new this.ManagedModel();
   }
 
   public async update() {
-    return this.instances;
+    return this.instance;
   }
 
   public async $validateTranslation() {
@@ -255,12 +250,13 @@ export class BaseManager {
   }
 
   public async $saveTranslation(attributes) {
-    const translation = this.instance.translations.find((translation) => {
+    const instance = this.instance as any;
+    const translation = instance.translations.find((translation) => {
       return translation.language === attributes.language;
     });
 
     if (!translation) {
-      await this.instance.related('translations').create(attributes);
+      await instance.related('translations').create(attributes);
     } else {
       translation.merge(attributes);
       await translation.save();
@@ -274,6 +270,30 @@ export class BaseManager {
     return this.byId();
   }
 
+  public async $updateLinks(instance, links) {
+    if (links) {
+      await instance.load('links');
+
+      let index = 0;
+      while (instance.links[index] || links[index]) {
+        const link = instance.links[index];
+        const url = links[index];
+
+        if (link && url) {
+          const link = instance.links[index];
+          link.url = links[index];
+          await link.save();
+        } else if (!link && url) {
+          await instance.related('links').create({ url });
+        } else if (link && !url) {
+          await link.delete();
+        }
+
+        index++;
+      }
+    }
+  }
+
   private $toResource(instance): Resource {
     const resource = new Resource(instance);
     resource.boot();
diff --git a/app/Helpers/Managers/LocationManager.ts b/app/Helpers/Managers/LocationManager.ts
new file mode 100644
index 00000000..0c4d915a
--- /dev/null
+++ b/app/Helpers/Managers/LocationManager.ts
@@ -0,0 +1,132 @@
+import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
+import BaseManager from 'App/Helpers/Managers/BaseManager';
+import Location from 'App/Models/Location';
+import {
+  CreateLocationValidator,
+  UpdateLocationValidator,
+} from 'App/Validators/v1/LocationValidator';
+import { LocationTranslationValidator } from 'App/Validators/v1/LocationTranslationValidator';
+import Database from '@ioc:Adonis/Lucid/Database';
+import Address from 'App/Models/Address';
+
+export default class LocationManager extends BaseManager<typeof Location> {
+  public ManagedModel = Location;
+
+  public settings = {
+    queryId: 'public_id',
+    orderableBy: [
+      {
+        name: 'name',
+        query: Database.raw(
+          `(SELECT name FROM organizer_translations WHERE organizer_translations.organizer_id = organizers.id AND organizer_translations.language = '${this.language}')`
+        ),
+      },
+      {
+        name: 'createdAt',
+        attribute: 'created_at',
+      },
+      {
+        name: 'updatedAt',
+        attribute: 'updated_at',
+      },
+    ],
+  };
+
+  public validators = {
+    translate: LocationTranslationValidator,
+  };
+
+  constructor(ctx: HttpContextContract) {
+    super(ctx, Location);
+  }
+
+  public query() {
+    return super.query().preload('address');
+  }
+
+  private async $createAddress(location, attributes, trx) {
+    const address = new Address();
+    await location.related();
+    address.fill(attributes);
+
+    address.useTransaction(trx);
+    await address.save();
+    await location.related('address').associate(address);
+
+    return address;
+  }
+
+  public async create() {
+    const { attributes, relations } = await this.ctx.request.validate(
+      new CreateLocationValidator(this.ctx)
+    );
+
+    const location = new Location();
+    await Database.transaction(async (trx) => {
+      location.useTransaction(trx);
+      await location.save();
+
+      await location.related('translations').create({
+        name: attributes.name,
+        description: attributes.description,
+        language: this.language,
+      });
+
+      if (relations?.address) {
+        await this.$createAddress(location, relations.address.attributes, trx);
+      }
+
+      await this.$updateLinks(location, relations?.links);
+    });
+
+    return await await this.byId(location.publicId);
+  }
+
+  public async update() {
+    const { attributes, relations } = await this.ctx.request.validate(
+      new UpdateLocationValidator(this.ctx)
+    );
+
+    const location = await this.byId();
+    await Database.transaction(async (trx) => {
+      location.useTransaction(trx);
+      if (location.$isDirty) {
+        await location.save();
+      }
+
+      if (relations?.address) {
+        if (location.address) {
+          location.address.merge(relations.address.attributes);
+          await location.address.save();
+        } else {
+          await this.$createAddress(
+            location,
+            relations.address.attributes,
+            trx
+          );
+        }
+      }
+
+      await this.$updateLinks(location, relations?.links);
+    });
+
+    return await this.byId(location.publicId);
+  }
+
+  public async translate() {
+    const attributes = await this.$validateTranslation();
+
+    // Creating an organizer translation without a name is forbidden,
+    // but initially creating one without a name is impossible. Hence fallback
+    // to the initial name
+    if (!attributes.name) {
+      attributes.name = this.instance?.translations?.find((translation) => {
+        return translation.name;
+      })?.name;
+    }
+
+    await this.$saveTranslation(attributes);
+
+    return this.byId();
+  }
+}
diff --git a/app/Helpers/Managers/OrganizerManager.ts b/app/Helpers/Managers/OrganizerManager.ts
index f2d92b58..12749d0b 100644
--- a/app/Helpers/Managers/OrganizerManager.ts
+++ b/app/Helpers/Managers/OrganizerManager.ts
@@ -1,4 +1,3 @@
-import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
 import BaseManager from 'App/Helpers/Managers/BaseManager';
 import Organizer, { OrganizerStatus } from 'App/Models/Organizer';
 import { withTranslations } from 'App/Helpers/Utilities';
@@ -10,8 +9,8 @@ import { OrganizerTranslationValidator } from 'App/Validators/v1/OrganizerTransl
 import Address from 'App/Models/Address';
 import Database from '@ioc:Adonis/Lucid/Database';
 
-export default class OrganizerManager extends BaseManager {
-  public ModelClass = Organizer;
+export default class OrganizerManager extends BaseManager<typeof Organizer> {
+  public ManagedModel = Organizer;
 
   public settings = {
     queryId: 'public_id',
@@ -81,7 +80,7 @@ export default class OrganizerManager extends BaseManager {
     translate: OrganizerTranslationValidator,
   };
 
-  constructor(ctx: HttpContextContract) {
+  constructor(ctx) {
     super(ctx, Organizer);
   }
 
@@ -109,30 +108,6 @@ export default class OrganizerManager extends BaseManager {
     }
   }
 
-  private async $updateLinks(organizer: Organizer, links) {
-    if (links) {
-      await organizer.load('links');
-
-      let index = 0;
-      while (organizer.links[index] || links[index]) {
-        const link = organizer.links[index];
-        const url = links[index];
-
-        if (link && url) {
-          const link = organizer.links[index];
-          link.url = links[index];
-          await link.save();
-        } else if (!link && url) {
-          await organizer.related('links').create({ url });
-        } else if (link && !url) {
-          await link.delete();
-        }
-
-        index++;
-      }
-    }
-  }
-
   public async create() {
     const { attributes, relations } = await this.ctx.request.validate(
       new CreateOrganizerValidator(this.ctx)
@@ -159,7 +134,7 @@ export default class OrganizerManager extends BaseManager {
       await this.$updateLinks(organizer, relations?.links);
     });
 
-    return await this.byId(organizer.publicId);
+    return await await this.byId(organizer.publicId);
   }
 
   public async update() {
@@ -167,7 +142,7 @@ export default class OrganizerManager extends BaseManager {
       new UpdateOrganizerValidator(this.ctx)
     );
 
-    const organizer = (await this.byId()) as Organizer;
+    const organizer = await this.byId();
     await Database.transaction(async (trx) => {
       organizer.homepage = attributes?.homepage || organizer.homepage;
       organizer.phone = attributes?.phone || organizer.phone;
@@ -207,9 +182,9 @@ export default class OrganizerManager extends BaseManager {
     // but initially creating one without a name is impossible. Hence fallback
     // to the initial name
     if (!attributes.name) {
-      attributes.name = this.instance.translations.find((translation) => {
+      attributes.name = this.instance?.translations?.find((translation) => {
         return translation.name;
-      }).name;
+      })?.name;
     }
 
     await this.$saveTranslation(attributes);
diff --git a/app/Helpers/Utilities.ts b/app/Helpers/Utilities.ts
index c1f3728b..1aeca2dc 100644
--- a/app/Helpers/Utilities.ts
+++ b/app/Helpers/Utilities.ts
@@ -1,3 +1,6 @@
+import Resource from 'App/Helpers/Api/Resource';
+import { validator } from '@ioc:Adonis/Core/Validator';
+
 export function withTranslations(query) {
   return query.preload('translations');
 }
@@ -11,3 +14,43 @@ export function findTranslation(translations, language?) {
     return translation.language === language;
   });
 }
+
+export async function publishable(
+  instance,
+  PublishableValidator,
+  PublishableTranslationValidator?
+) {
+  const resource = new Resource(instance).boot().toObject();
+
+  const errors = {};
+  try {
+    await validator.validate({
+      schema: new PublishableValidator().schema,
+      data: resource,
+    });
+  } catch (e) {
+    Object.assign(errors, e.messages);
+  }
+
+  if (PublishableTranslationValidator) {
+    // Use an empty object to validate against, to force the error
+    // even if there are no translations at all
+    const translations = resource.relations?.translations || [{}];
+    for (const translation of translations) {
+      try {
+        await validator.validate({
+          schema: new PublishableTranslationValidator().schema,
+          data: translation,
+        });
+
+        // Stop validating if there is only one valid
+        // translation
+        break;
+      } catch (e) {
+        Object.assign(errors, e.messages);
+      }
+    }
+  }
+
+  return Object.keys(errors).length ? errors : true;
+}
diff --git a/app/Models/Location.ts b/app/Models/Location.ts
new file mode 100644
index 00000000..50677c3d
--- /dev/null
+++ b/app/Models/Location.ts
@@ -0,0 +1,92 @@
+import { DateTime } from 'luxon';
+import {
+  BaseModel,
+  column,
+  manyToMany,
+  ManyToMany,
+  hasMany,
+  HasMany,
+  belongsTo,
+  BelongsTo,
+  beforeCreate,
+} from '@ioc:Adonis/Lucid/Orm';
+import { cuid } from '@ioc:Adonis/Core/Helpers';
+import { PublishLocationValidator } from 'App/Validators/v1/LocationValidator';
+import { PublishLocationTranslationValidator } from 'App/Validators/v1/LocationTranslationValidator';
+import Address from 'App/Models/Address';
+import Link from 'App/Models/Link';
+import { publishable } from 'App/Helpers/Utilities';
+
+export class LocationTranslation extends BaseModel {
+  @column({ isPrimary: true, serializeAs: null })
+  public id: number;
+
+  @column()
+  public language: string;
+
+  @column()
+  public name: string;
+
+  @column()
+  public description: string;
+
+  @column({ serializeAs: null })
+  public locationId: number;
+}
+
+export enum LocationStatus {
+  DRAFT = 'draft',
+  PUBLISHED = 'published',
+}
+
+export default class Location extends BaseModel {
+  @column({ isPrimary: true, serializeAs: null })
+  public id: string;
+
+  @column({ serializeAs: null })
+  public publicId: string;
+
+  @column()
+  public status: string;
+
+  @column({ serializeAs: null })
+  public addressId: number;
+
+  @belongsTo(() => Address)
+  public address: BelongsTo<typeof Address>;
+
+  @hasMany(() => LocationTranslation)
+  public translations: HasMany<typeof LocationTranslation>;
+
+  @manyToMany(() => Link, {
+    relatedKey: 'id',
+    localKey: 'publicId',
+    pivotForeignKey: 'location_public_id',
+    pivotRelatedForeignKey: 'link_id',
+    pivotTable: 'location_links',
+  })
+  public links: ManyToMany<typeof Link>;
+
+  @column.dateTime({ autoCreate: true })
+  public createdAt: DateTime;
+
+  @column.dateTime({ autoCreate: true, autoUpdate: true })
+  public updatedAt: DateTime;
+
+  public async publishable() {
+    return publishable(
+      this,
+      PublishLocationValidator,
+      PublishLocationTranslationValidator
+    );
+  }
+
+  @beforeCreate()
+  public static async setPublicId(location: Location) {
+    if (location.publicId) {
+      return;
+    }
+
+    location.publicId = cuid();
+  }
+}
diff --git a/app/Models/Organizer.ts b/app/Models/Organizer.ts
index 153758a5..af5abbdb 100644
--- a/app/Models/Organizer.ts
+++ b/app/Models/Organizer.ts
@@ -14,11 +14,10 @@ import { cuid } from '@ioc:Adonis/Core/Helpers';
 import Address from 'App/Models/Address';
 import OrganizerType from 'App/Models/OrganizerType';
 import OrganizerSubject from 'App/Models/OrganizerSubject';
-import { validator } from '@ioc:Adonis/Core/Validator';
 import { PublishOrganizerValidator } from 'App/Validators/v1/OrganizerValidator';
 import { PublishOrganizerTranslationValidator } from 'App/Validators/v1/OrganizerTranslationValidator';
-import Resource from 'App/Helpers/Api/Resource';
-import Link from './Link';
+import Link from 'App/Models/Link';
+import { publishable } from 'App/Helpers/Utilities';
 
 export class OrganizerTranslation extends BaseModel {
   @column({ isPrimary: true, serializeAs: null })
@@ -53,37 +52,11 @@ export default class Organizer extends BaseModel {
   public status: string;
 
   public async publishable() {
-    const resource = new Resource(this).boot().toObject();
-
-    const errors = {};
-    try {
-      await validator.validate({
-        schema: new PublishOrganizerValidator(this).schema,
-        data: resource,
-      });
-    } catch (e) {
-      Object.assign(errors, e.messages);
-    }
-
-    // Use an empty object to validate against, to force the error
-    // even if there are no translations at all
-    const translations = resource.relations?.translations || [{}];
-    for (const translation of translations) {
-      try {
-        await validator.validate({
-          schema: new PublishOrganizerTranslationValidator().schema,
-          data: translation,
-        });
-
-        // Stop validating if there is only one valid
-        // translation
-        break;
-      } catch (e) {
-        Object.assign(errors, e.messages);
-      }
-    }
-
-    return Object.keys(errors).length ? errors : true;
+    return publishable(
+      this,
+      PublishOrganizerValidator,
+      PublishOrganizerTranslationValidator
+    );
   }
 
   @column({ serializeAs: null })
diff --git a/app/Validators/v1/LocationTranslationValidator.ts b/app/Validators/v1/LocationTranslationValidator.ts
new file mode 100644
index 00000000..12afc623
--- /dev/null
+++ b/app/Validators/v1/LocationTranslationValidator.ts
@@ -0,0 +1,31 @@
+import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
+import { schema } from '@ioc:Adonis/Core/Validator';
+import { allowedLanguages } from 'Config/app';
+
+export class LocationTranslationValidator {
+  constructor(private context: HttpContextContract) {}
+
+  public schema = schema.create({
+    attributes: schema.object().members({
+      name: schema.string.optional({ trim: true }),
+      description: schema.string.optional({ trim: true }),
+      language: schema.enum(allowedLanguages),
+    }),
+  });
+
+  public cacheKey = this.context.routeKey;
+
+  public messages = {};
+}
+
+export class PublishLocationTranslationValidator {
+  public schema = schema.create({
+    attributes: schema.object().members({
+      name: schema.string({ trim: true }),
+      description: schema.string({ trim: true }),
+      language: schema.enum(allowedLanguages),
+    }),
+  });
+
+  public messages = {};
+}
diff --git a/app/Validators/v1/LocationValidator.ts b/app/Validators/v1/LocationValidator.ts
new file mode 100644
index 00000000..0e42dbdd
--- /dev/null
+++ b/app/Validators/v1/LocationValidator.ts
@@ -0,0 +1,86 @@
+import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
+import { schema, rules } from '@ioc:Adonis/Core/Validator';
+import { LocationStatus } from 'App/Models/Location';
+
+export class CreateLocationValidator {
+  constructor(private context: HttpContextContract) {}
+
+  public schema = schema.create({
+    attributes: schema.object().members({
+      name: schema.string({ trim: true }),
+      description: schema.string.optional({ trim: true }),
+      status: schema.enum.optional(Object.values(LocationStatus)),
+    }),
+    relations: schema.object.optional().members({
+      address: schema.object.optional().members({
+        attributes: schema.object().members({
+          street1: schema.string({ trim: true }),
+          street2: schema.string.optional({ trim: true }),
+          zipCode: schema.string({ trim: true }),
+          city: schema.string({ trim: true }),
+        }),
+      }),
+      links: schema.array
+        .optional([rules.maxLength(3)])
+        .members(schema.string({}, [rules.url()])),
+    }),
+  });
+
+  public cacheKey = this.context.routeKey;
+
+  public messages = {};
+}
+
+export class UpdateLocationValidator {
+  constructor(private context: HttpContextContract) {}
+
+  public schema = schema.create({
+    attributes: schema.object.optional().members({
+      status: schema.enum.optional(Object.values(LocationStatus)),
+    }),
+    relations: schema.object.optional().members({
+      address: schema.object.optional().members({
+        attributes: schema.object().members({
+          street1: schema.string.optional({ trim: true }),
+          street2: schema.string.optional({ trim: true }),
+          zipCode: schema.string.optional({ trim: true }),
+          city: schema.string.optional({ trim: true }),
+        }),
+      }),
+      links: schema.array
+        .optional([rules.maxLength(3)])
+        .members(schema.string({}, [rules.url()])),
+    }),
+  });
+
+  public cacheKey = this.context.routeKey;
+
+  public messages = {};
+}
+
+export class PublishLocationValidator {
+  constructor(private context: HttpContextContract) {}
+
+  public schema = schema.create({
+    attributes: schema.object().members({
+      status: schema.enum(Object.values(LocationStatus)),
+    }),
+    relations: schema.object().members({
+      address: schema.object().members({
+        attributes: schema.object().members({
+          street1: schema.string({ trim: true }),
+          street2: schema.string.optional({ trim: true }),
+          zipCode: schema.string({ trim: true }),
+          city: schema.string({ trim: true }),
+        }),
+      }),
+      links: schema.array
+        .optional([rules.maxLength(3)])
+        .members(schema.string({}, [rules.url()])),
+    }),
+  });
+
+  public cacheKey = this.context.routeKey;
+
+  public messages = {};
+}
diff --git a/database/migrations/1625644181250_locations.ts b/database/migrations/1625644181250_locations.ts
new file mode 100644
index 00000000..ef069cf9
--- /dev/null
+++ b/database/migrations/1625644181250_locations.ts
@@ -0,0 +1,25 @@
+import BaseSchema from '@ioc:Adonis/Lucid/Schema';
+import { LocationStatus } from 'App/Models/Location';
+
+export default class Locations extends BaseSchema {
+  protected tableName = 'locations';
+
+  public async up() {
+    this.schema.createTable(this.tableName, (table) => {
+      table.increments('id').primary();
+      table.string('public_id').notNullable().unique();
+
+      table
+        .enu('status', [LocationStatus.DRAFT, LocationStatus.PUBLISHED])
+        .defaultTo(LocationStatus.DRAFT);
+
+      table.integer('address_id').unsigned().references('addresses.id');
+
+      table.timestamps(true);
+    });
+  }
+
+  public async down() {
+    this.schema.dropTable(this.tableName);
+  }
+}
diff --git a/database/migrations/1625644181255_location_translations.ts b/database/migrations/1625644181255_location_translations.ts
new file mode 100644
index 00000000..061a42eb
--- /dev/null
+++ b/database/migrations/1625644181255_location_translations.ts
@@ -0,0 +1,25 @@
+import BaseSchema from '@ioc:Adonis/Lucid/Schema';
+import { Languages } from 'App/Helpers/Languages';
+
+export default class LocationTranslations extends BaseSchema {
+  protected tableName = 'location_translations';
+
+  public async up() {
+    this.schema.createTable(this.tableName, (table) => {
+      table.increments('id').primary();
+
+      table
+        .enu('language', [Languages.DE, Languages.EN])
+        .defaultTo(Languages.DE);
+
+      table.integer('location_id').unsigned().references('locations.id');
+
+      table.string('name').notNullable();
+      table.text('description');
+    });
+  }
+
+  public async down() {
+    this.schema.dropTable(this.tableName);
+  }
+}
diff --git a/database/migrations/1625644181260_location_links.ts b/database/migrations/1625644181260_location_links.ts
new file mode 100644
index 00000000..d67429c8
--- /dev/null
+++ b/database/migrations/1625644181260_location_links.ts
@@ -0,0 +1,27 @@
+import BaseSchema from '@ioc:Adonis/Lucid/Schema';
+
+export default class LocationLinks extends BaseSchema {
+  protected tableName = 'location_links';
+
+  public async up() {
+    this.schema.createTable(this.tableName, (table) => {
+      table.increments('id');
+      table.timestamps(true);
+
+      table
+        .string('location_public_id')
+        .unsigned()
+        .references('locations.public_id')
+        .onDelete('CASCADE');
+      table
+        .integer('link_id')
+        .unsigned()
+        .references('links.id')
+        .onDelete('CASCADE');
+    });
+  }
+
+  public async down() {
+    this.schema.dropTable(this.tableName);
+  }
+}
diff --git a/start/routes.ts b/start/routes.ts
index d5eaec4d..8cd7f53b 100644
--- a/start/routes.ts
+++ b/start/routes.ts
@@ -56,5 +56,8 @@ Route.group(() => {
       'organizerType.organizerSubject',
       'v1/OrganizerSubjectController'
     ).apiOnly();
+
+    Route.resource('location', 'v1/LocationController').apiOnly();
+    Route.post('location/:id/translate', 'v1/LocationController.translate');
   });
 }).prefix('v1');