diff --git a/app/server.ts b/app/server.ts
index e35c5f2c8282a642facfe3d240243309b952efd1..11bcc28a4cb11c9d3b5d94c5dbba3705dfad96ec 100644
--- a/app/server.ts
+++ b/app/server.ts
@@ -1,9 +1,10 @@
 import { logger } from './adapters/Logger.ts'
 import { GeoApiClient } from './adapters/geo/GeoApiClient.ts'
-import { createSchema, createYoga } from './deps.ts'
+import { createSchema, createYoga, GraphQLError } from './deps.ts'
 import { QueryEvents, QueryEventsInput, QueryEventsOutput } from './usecases/QueryEvents.ts'
 import { JasdGateway } from './adapters/jasd/JasdGateway.ts'
 import { QueryEvent, QueryEventInput, QueryEventOutput } from './usecases/QueryEvent.ts'
+import { QueryNearbyEvents, QueryNearbyEventsInput, QueryNearbyEventsOutput } from './usecases/QueryNearbyEvents.ts'
 
 const SCHEMA = `
     type Location {
@@ -36,13 +37,18 @@ const SCHEMA = `
       data: [Event]
     }
 
+    type NearbyEventsResponse {
+      data: [Event]
+    }
+    
     type EventResponse {
       data: Event
     }
     
     type Query {
-      # uses offset-based pagination as described in https://www.apollographql.com/docs/react/pagination/offset-based
-      events(offset: Int = 0, limit: Int = 5, geolocationId: ID, localOnly: Boolean): EventsResponse!
+      # offset-based pagination https://www.apollographql.com/docs/react/pagination/offset-based
+      events(offset: Int = 0, limit: Int = 5, localOnly: Boolean): EventsResponse!
+      nearbyEvents(offset: Int = 0, limit: Int = 5, geolocationId: ID!): NearbyEventsResponse!
       event(id: String!): EventResponse!
     }
 `
@@ -54,6 +60,12 @@ export type EventsRequest = {
   localOnly?: boolean
 }
 
+export type NearbyEventsRequest = {
+  limit?: number
+  offset?: number
+  geolocationId: string
+}
+
 export type EventRequest = {
   id: string
 }
@@ -74,6 +86,23 @@ const validateQueryEventsInput = (request: EventsRequest): QueryEventsInput => {
   }
 }
 
+const validateQueryNearbyEventsInput = (request: NearbyEventsRequest): QueryNearbyEventsInput => {
+  logger.debug('validating request: ' + JSON.stringify(request))
+
+  let limit = request.limit ?? 5
+  if (limit > 50) {
+    limit = 50
+  }
+
+  if (!request.geolocationId) throw new GraphQLError('GeolocationId not present')
+
+  return {
+    limit: limit,
+    offset: request.offset ?? 0,
+    geolocationId: request.geolocationId,
+  }
+}
+
 const validateQueryEventInput = (request: EventRequest): QueryEventInput => {
   logger.debug('validating request: ' + JSON.stringify(request))
   return {
@@ -113,10 +142,25 @@ const createResolvers = (config: ServerConfig, useCases: UseCases) => ({
         return useCases.QueryEvents.useCase.execute(input)
       }
     },
+    nearbyEvents: (
+      // deno-lint-ignore no-explicit-any
+      _parent: any,
+      parameters: EventsRequest,
+      // next line is required for the resolver to work
+      // deno-lint-ignore no-unused-vars
+      context: GraphQLContext,
+    ): Promise<QueryNearbyEventsOutput> => {
+      if (config.fake) {
+        return Promise.resolve({ data: [] })
+      } else {
+        const input = useCases.QueryEvents.validateInput(parameters)
+        return useCases.QueryEvents.useCase.execute(input)
+      }
+    },
   },
 })
 
-type UseCaseNames = 'QueryEvents' | 'QueryEvent'
+type UseCaseNames = 'QueryEvents' | 'QueryEvent' | 'QueryNearbyEvents'
 type UseCases = Record<UseCaseNames, { useCase: UseCase; validateInput: ValidateInputFn }>
 // deno-lint-ignore no-explicit-any
 type UseCase = { execute: (input: any) => any }
@@ -127,14 +171,18 @@ export const startServer = (config: ServerConfig): Deno.HttpServer<Deno.NetAddr>
   const geoApi = new GeoApiClient(config.geoAPIEndpointUrl, logger)
   const jasdEventsGateway = new JasdGateway(logger)
 
-  const useCases = {
+  const useCases: UseCases = {
     QueryEvent: {
       validateInput: validateQueryEventInput,
       useCase: new QueryEvent(jasdEventsGateway, logger),
     },
     QueryEvents: {
       validateInput: validateQueryEventsInput,
-      useCase: new QueryEvents(jasdEventsGateway, geoApi, logger),
+      useCase: new QueryEvents(jasdEventsGateway, logger),
+    },
+    QueryNearbyEvents: {
+      validateInput: validateQueryNearbyEventsInput,
+      useCase: new QueryNearbyEvents(jasdEventsGateway, geoApi, logger),
     },
   }
 
diff --git a/app/usecases/QueryEvents.test.ts b/app/usecases/QueryEvents.test.ts
index 67b2630a02b01339d01021dc19d1e1d86eefa1bd..ad4ee6b07265224002bbbdb148652064fd36cb34 100644
--- a/app/usecases/QueryEvents.test.ts
+++ b/app/usecases/QueryEvents.test.ts
@@ -1,19 +1,11 @@
 import { assertEquals } from '@std/assert'
 import { describe, it } from '@std/testing/bdd'
-import { returnsNext, stub } from '@std/testing/mock'
 
 import { QueryEvents } from './QueryEvents.ts'
-import { ActivityResult, Address, Location } from '../adapters/jasd/types/appapi.dto.types.ts'
-import { LocalTimedActivity, LocalTimedEvent, OnlineTimedActivity } from '../adapters/jasd/tests/fixtures.ts'
-import { JasdGateway } from '../adapters/jasd/JasdGateway.ts'
+import { ActivityResult } from '../adapters/jasd/types/appapi.dto.types.ts'
+import { LocalTimedEvent } from '../adapters/jasd/tests/fixtures.ts'
 
 describe('QueryEvents', () => {
-  const geoApiAMock = {
-    resolvePlace() {
-      return Promise.resolve({ name: 'Village', city: 'Municipality', state: 'State' })
-    },
-  }
-
   it('can make sense of an empty response', async () => {
     const jasdGatewayMock = {
       fetchActivities() {
@@ -21,7 +13,7 @@ describe('QueryEvents', () => {
       },
     }
 
-    const sut = new QueryEvents(jasdGatewayMock, geoApiAMock, console)
+    const sut = new QueryEvents(jasdGatewayMock, console)
 
     const result = await sut.execute({ offset: 0, limit: 1, localOnly: false })
 
@@ -41,7 +33,7 @@ describe('QueryEvents', () => {
       },
     }
 
-    const sut = new QueryEvents(jasdGatewayMock, geoApiAMock, console)
+    const sut = new QueryEvents(jasdGatewayMock, console)
 
     const result = await sut.execute({ offset: 0, limit: 1, localOnly: false })
 
@@ -72,123 +64,4 @@ describe('QueryEvents', () => {
       totalResults: 1,
     })
   })
-
-  describe('when requesting 5 events', () => {
-    it('it puts a events in the users city first in the response', async () => {
-      const activitiesForCityResponse = {
-        events: [
-          {
-            name: 'a',
-            description: 'description',
-            resultType: 'ACTIVITY',
-            activity: LocalTimedActivity,
-            location: aLocation({ address: anAddress({ city: 'Municipality' }) }),
-          },
-          {
-            name: 'b',
-            description: 'description',
-            resultType: 'ACTIVITY',
-            activity: LocalTimedActivity,
-            location: aLocation({ address: anAddress({ city: 'Municipality' }) }),
-          },
-        ],
-        totalCount: 2,
-      }
-
-      const activitiesForStateResponse = {
-        events: [
-          {
-            name: 'c',
-            description: 'description',
-            resultType: 'ACTIVITY',
-            activity: LocalTimedActivity,
-            location: aLocation({ address: anAddress({ state: 'State' }) }),
-          },
-        ],
-        totalCount: 1,
-      }
-      const activitiesUnfilteredResponse = {
-        events: [
-          {
-            name: 'd',
-            description: 'description',
-            resultType: 'ACTIVITY',
-            activity: LocalTimedActivity,
-            location: LocalTimedActivity.location,
-          },
-          {
-            name: 'e',
-            description: 'description',
-            resultType: 'ACTIVITY',
-            activity: OnlineTimedActivity,
-            location: OnlineTimedActivity.location,
-          },
-        ],
-        totalCount: 2,
-      }
-
-      const fetchActivitiesSpy = stub(
-        {} as JasdGateway,
-        'fetchActivities',
-        returnsNext([
-          Promise.resolve(activitiesForCityResponse),
-          Promise.resolve(activitiesForStateResponse),
-          Promise.resolve(activitiesUnfilteredResponse),
-        ]),
-      )
-
-      const sut = new QueryEvents(
-        {
-          fetchActivities: fetchActivitiesSpy,
-        },
-        geoApiAMock,
-        console,
-      )
-
-      const result = await sut.execute({
-        limit: 5,
-        offset: 0,
-        localOnly: false,
-        geolocationId: 'someGeolocationId',
-      })
-
-      assertEquals(result.totalResults, 5)
-      assertEquals(result.data[0].title, 'a')
-      assertEquals(result.data[1].title, 'b')
-      assertEquals(result.data[2].title, 'c')
-      assertEquals(result.data[3].title, 'd')
-      assertEquals(result.data[4].title, 'e')
-    })
-  })
 })
-
-// todo gregor: test remote events in the location-based set are ignored
-
-const aLocation = (override?: Partial<Location>): Location => {
-  const fallback: Location = {
-    address: anAddress(),
-    coordinate: {
-      coordinates: [0, 0],
-      type: 'Point',
-    },
-    online: false,
-    privateLocation: false,
-    url: '',
-  }
-
-  return { ...fallback, ...override }
-}
-
-const anAddress = (override?: Partial<Address>): Address => {
-  const fallback = {
-    city: '',
-    country: null,
-    name: '',
-    state: null,
-    street: '',
-    streetNo: '',
-    supplement: null,
-    zipCode: '',
-  }
-  return { ...fallback, ...override }
-}
diff --git a/app/usecases/QueryEvents.ts b/app/usecases/QueryEvents.ts
index 27f0d5154e6c274ffa9e1784be558cc24a08e8a2..afcc988215e8dff67e0c454b2e363ceb28102df7 100644
--- a/app/usecases/QueryEvents.ts
+++ b/app/usecases/QueryEvents.ts
@@ -1,5 +1,4 @@
 import { FetchesJasdActivities } from './dependencies/FetchesJasdActivities.ts'
-import { ResolvesPlace } from './dependencies/ResolvesCity.ts'
 import { HoliEvent } from '../domain/HoliEvent.ts'
 import { Logs } from './dependencies/Logs.ts'
 import { ActivityResult } from '../adapters/jasd/types/appapi.dto.types.ts'
@@ -21,7 +20,6 @@ const APP_FILES_BASE_URL = 'https://gemeinschaftswerk-nachhaltigkeit.de/app/api/
 export class QueryEvents {
   constructor(
     private readonly jasdApi: FetchesJasdActivities,
-    private readonly geoApi: ResolvesPlace,
     private readonly logger: Logs,
   ) {}
 
@@ -30,55 +28,17 @@ export class QueryEvents {
     const startTime = Date.now()
 
     try {
-      const placeDetails = input.geolocationId ? await this.geoApi.resolvePlace(input.geolocationId) : undefined
-
-      const eventsToFind = input.limit
-      const holiEvents: HoliEvent[] = []
-      if (input.geolocationId) {
-        const cityActivities = await this.jasdApi.fetchActivities(
-          input.limit,
-          input.offset,
-          input.localOnly,
-          placeDetails?.city,
-        )
-        const holiCityEvents = cityActivities.events.map(this.toDomainEvent)
-        holiEvents.push(...holiCityEvents)
-
-        if (holiEvents.length >= eventsToFind) {
-          return {
-            data: holiEvents,
-            totalResults: holiEvents.length,
-          }
-        }
-
-        const stateActivities = await this.jasdApi.fetchActivities(
-          input.limit,
-          input.offset,
-          input.localOnly,
-          placeDetails?.state,
-        )
-        const holiStateEvents = stateActivities.events.map(this.toDomainEvent)
-        holiEvents.push(...holiStateEvents)
-
-        if (holiEvents.length >= eventsToFind) {
-          return {
-            data: holiEvents,
-            totalResults: holiEvents.length,
-          }
-        }
-      }
-
-      const nationalOnlineAndLocalEvents = await this.jasdApi.fetchActivities(
-        input.limit - holiEvents.length,
+      const jasdActivities = await this.jasdApi.fetchActivities(
+        input.limit,
         input.offset,
         input.localOnly,
       )
 
-      holiEvents.push(...(nationalOnlineAndLocalEvents.events.map(this.toDomainEvent)))
+      const holiEvents = jasdActivities.events.map(this.toDomainEvent)
 
       return {
         data: holiEvents,
-        totalResults: holiEvents.length,
+        totalResults: jasdActivities.totalCount,
       }
     } catch (error) {
       this.logger.error('Error executing QueryEvents use case', error)
diff --git a/app/usecases/QueryNearbyEvents.test.ts b/app/usecases/QueryNearbyEvents.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..bc022c33fba08ff88032c1ba4b4a62d7246943df
--- /dev/null
+++ b/app/usecases/QueryNearbyEvents.test.ts
@@ -0,0 +1,134 @@
+import { assertEquals } from '@std/assert'
+import { describe, it } from '@std/testing/bdd'
+import { returnsNext, stub } from '@std/testing/mock'
+
+import { Address, Location } from '../adapters/jasd/types/appapi.dto.types.ts'
+import { LocalTimedActivity, OnlineTimedActivity } from '../adapters/jasd/tests/fixtures.ts'
+import { JasdGateway } from '../adapters/jasd/JasdGateway.ts'
+import { QueryNearbyEvents } from './QueryNearbyEvents.ts'
+
+describe('QueryNearbyEvents', () => {
+  const geoApiAMock = {
+    resolvePlace() {
+      return Promise.resolve({ name: 'Village', city: 'Municipality', state: 'State' })
+    },
+  }
+
+  describe('when requesting 5 events', () => {
+    it('it puts a events in the users city first in the response', async () => {
+      const activitiesForCityResponse = {
+        events: [
+          {
+            name: 'a',
+            description: 'description',
+            resultType: 'ACTIVITY',
+            activity: LocalTimedActivity,
+            location: aLocation({ address: anAddress({ city: 'Municipality' }) }),
+          },
+          {
+            name: 'b',
+            description: 'description',
+            resultType: 'ACTIVITY',
+            activity: LocalTimedActivity,
+            location: aLocation({ address: anAddress({ city: 'Municipality' }) }),
+          },
+        ],
+        totalCount: 2,
+      }
+
+      const activitiesForStateResponse = {
+        events: [
+          {
+            name: 'c',
+            description: 'description',
+            resultType: 'ACTIVITY',
+            activity: LocalTimedActivity,
+            location: aLocation({ address: anAddress({ state: 'State' }) }),
+          },
+        ],
+        totalCount: 1,
+      }
+      const activitiesUnfilteredResponse = {
+        events: [
+          {
+            name: 'd',
+            description: 'description',
+            resultType: 'ACTIVITY',
+            activity: LocalTimedActivity,
+            location: LocalTimedActivity.location,
+          },
+          {
+            name: 'e',
+            description: 'description',
+            resultType: 'ACTIVITY',
+            activity: OnlineTimedActivity,
+            location: OnlineTimedActivity.location,
+          },
+        ],
+        totalCount: 2,
+      }
+
+      const fetchActivitiesSpy = stub(
+        {} as JasdGateway,
+        'fetchActivities',
+        returnsNext([
+          Promise.resolve(activitiesForCityResponse),
+          Promise.resolve(activitiesForStateResponse),
+          Promise.resolve(activitiesUnfilteredResponse),
+        ]),
+      )
+
+      const sut = new QueryNearbyEvents(
+        {
+          fetchActivities: fetchActivitiesSpy,
+        },
+        geoApiAMock,
+        console,
+      )
+
+      const result = await sut.execute({
+        limit: 5,
+        offset: 0,
+        geolocationId: 'someGeolocationId',
+      })
+
+      assertEquals(result.data.length, 5)
+      assertEquals(result.data[0].title, 'a')
+      assertEquals(result.data[1].title, 'b')
+      assertEquals(result.data[2].title, 'c')
+      assertEquals(result.data[3].title, 'd')
+      assertEquals(result.data[4].title, 'e')
+    })
+  })
+})
+
+// todo gregor: test remote events in the location-based set are ignored
+
+const aLocation = (override?: Partial<Location>): Location => {
+  const fallback: Location = {
+    address: anAddress(),
+    coordinate: {
+      coordinates: [0, 0],
+      type: 'Point',
+    },
+    online: false,
+    privateLocation: false,
+    url: '',
+  }
+
+  return { ...fallback, ...override }
+}
+
+const anAddress = (override?: Partial<Address>): Address => {
+  const fallback = {
+    city: '',
+    country: null,
+    name: '',
+    state: null,
+    street: '',
+    streetNo: '',
+    supplement: null,
+    zipCode: '',
+  }
+  return { ...fallback, ...override }
+}
diff --git a/app/usecases/QueryNearbyEvents.ts b/app/usecases/QueryNearbyEvents.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c875e072597a17f3a838ce9f8cfc19357101eefc
--- /dev/null
+++ b/app/usecases/QueryNearbyEvents.ts
@@ -0,0 +1,107 @@
+import { FetchesJasdActivities } from './dependencies/FetchesJasdActivities.ts'
+import { ResolvesPlace } from './dependencies/ResolvesCity.ts'
+import { HoliEvent } from '../domain/HoliEvent.ts'
+import { Logs } from './dependencies/Logs.ts'
+import { ActivityResult } from '../adapters/jasd/types/appapi.dto.types.ts'
+
+export interface QueryNearbyEventsInput {
+  limit: number
+  offset: number
+  geolocationId?: string
+}
+
+export interface QueryNearbyEventsOutput {
+  data: HoliEvent[]
+}
+
+const APP_FILES_BASE_URL = 'https://gemeinschaftswerk-nachhaltigkeit.de/app/api/v1/files'
+
+export class QueryNearbyEvents {
+  constructor(
+    private readonly jasdApi: FetchesJasdActivities,
+    private readonly geoApi: ResolvesPlace,
+    private readonly logger: Logs,
+  ) {}
+
+  async execute(input: QueryNearbyEventsInput): Promise<QueryNearbyEventsOutput> {
+    this.logger.debug(`Executing QueryEvents use case with input: ${JSON.stringify(input)}`)
+    const startTime = Date.now()
+
+    try {
+      const placeDetails = input.geolocationId ? await this.geoApi.resolvePlace(input.geolocationId) : undefined
+
+      const eventsToFind = input.limit
+      const holiEvents: HoliEvent[] = []
+      const cityActivities = await this.jasdApi.fetchActivities(
+        input.limit,
+        input.offset,
+        false,
+        placeDetails?.city,
+      )
+      const holiCityEvents = cityActivities.events.map(this.toDomainEvent)
+      holiEvents.push(...holiCityEvents)
+
+      if (holiEvents.length >= eventsToFind) {
+        return {
+          data: holiEvents,
+        }
+      }
+
+      const stateActivities = await this.jasdApi.fetchActivities(
+        input.limit,
+        input.offset,
+        false,
+        placeDetails?.state,
+      )
+      const holiStateEvents = stateActivities.events.map(this.toDomainEvent)
+      holiEvents.push(...holiStateEvents)
+
+      if (holiEvents.length >= eventsToFind) {
+        return {
+          data: holiEvents,
+        }
+      }
+
+      const nationalOnlineAndLocalEvents = await this.jasdApi.fetchActivities(
+        input.limit - holiEvents.length,
+        input.offset,
+        false,
+      )
+
+      holiEvents.push(...(nationalOnlineAndLocalEvents.events.map(this.toDomainEvent)))
+
+      return {
+        data: holiEvents,
+      }
+    } catch (error) {
+      this.logger.error('Error executing QueryEvents use case', error)
+      throw error
+    } finally {
+      const duration = Date.now() - startTime
+      this.logger.info(`QueryEvents use case executed in ${duration}ms`)
+    }
+  }
+
+  private toDomainEvent(event: ActivityResult): HoliEvent {
+    return {
+      id: event.activity.id.toString(),
+      title: event.name,
+      organisationName: event.activity.organisation.name,
+      startDate: event.activity.period.start,
+      endDate: event.activity.period.end,
+      location: {
+        city: event.location.address.city,
+        street: event.location.address.street,
+        streetNo: event.location.address.streetNo,
+        zipCode: event.location.address.zipCode,
+      },
+      isRemote: event.activity.location.online,
+      imageUrl: `${APP_FILES_BASE_URL}/${event.activity.image}`,
+      eventDetails: {
+        description: event.description,
+        externalLink: event.activity.location.url,
+        meetingLink: event.activity.registerUrl ?? undefined,
+      },
+    }
+  }
+}
diff --git a/smoketest/main.js b/smoketest/main.js
index 41a4b5bc18df6822ffa686b27d0778e8f2ffe4ab..9f9951fc5efe25a15faa985b378195906bb64e92 100644
--- a/smoketest/main.js
+++ b/smoketest/main.js
@@ -29,6 +29,16 @@ export default () => {
     })
   })
 
+  forQuery(
+    `{ nearbyEvents(geolocationId: "511b707c992900284059c4dd733cc1874a40f00101f9019a4a930000000000c002059203084b72616d70666572") { data { id } } }`,
+    (response) => {
+      check(JSON.parse(response.body), {
+        'returns 5 nearbyEvents': (r) =>
+          Array.isArray(r.data.nearbyEvents.data) && r.data.nearbyEvents.data.length === 5,
+      })
+    },
+  )
+
   forQuery(`{ event(id: "1234") { data { id } } }`, (response) => {
     check(JSON.parse(response.body), {
       'returns event': (r) => r.data.event.data.id === '1234',