Zum Hauptinhalt springen
Echtzeit-Radar & Feeds
Alle RSS Feeds ➔
👥 Community & Social
Windows Tipps & SecurityGrafikkarte vor Überhitzung schützen: So geht’s(25.09.2026 um 08:00 Uhr)
••••••••••
Windows Tipps & SecurityGrafikkarte vor Überhitzung schützen: So geht’s(25.09.2026 um 08:00 Uhr)
••••••••••
Intelligence View
⚡ tsecurity.de Intelligence

Building a Type-Safe Backend with tRPC, Zod, and Prisma

Type safety is one of TypeScript's greatest strengths, but maintaining it across your entire stack—from database to API to frontend—can be challenging. In this article, I'll show you how to build a fully type-safe backend using tRPC, Zod, a…

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!

Type safety is one of TypeScript's greatest strengths, but maintaining it across your entire stack—from database to API to frontend—can be challenging. In this article, I'll show you how to build a fully type-safe backend using tRPC, Zod, and Prisma, and demonstrate the power of this combination through practical examples.






Why This Stack?



Before diving into the implementation, let's understand why this combination is powerful:





  • Prisma: Type-safe database access with auto-generated types from your schema


  • Zod: Runtime validation with TypeScript type inference


  • tRPC: End-to-end type safety without code generation, where your API types are automatically shared between client and server



The magic happens when these three work together: Prisma generates types from your database, Zod validates input at runtime while inferring TypeScript types, and tRPC ensures your frontend knows exactly what your backend expects and returns.






Project Setup



First, let's set up our project with the necessary dependencies:




npm init -y
npm install @trpc/server @trpc/client zod prisma @prisma/client
npm install -D typescript @types/node tsx

# Initialize Prisma
npx prisma init






Configure your tsconfig.json:




{
"compilerOptions": {
"target": "ES2022",
"module": "commonjs",
"lib": ["ES2022"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"moduleResolution": "node"
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}









Designing Your Database Schema



Let's create a practical example: a booking management system. This could apply to hotels, rental services, appointment scheduling, or any domain requiring reservation management. Here's our Prisma schema:




// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}

datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}

model Guest {
id String @id @default(cuid())
email String @unique
firstName String
lastName String
phone String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

bookings Booking[]
}

model Room {
id String @id @default(cuid())
name String
description String?
capacity Int
pricePerNight Decimal @db.Decimal(10, 2)
isAvailable Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

bookings Booking[]
}

model Booking {
id String @id @default(cuid())
guestId String
roomId String
checkIn DateTime
checkOut DateTime
totalPrice Decimal @db.Decimal(10, 2)
status BookingStatus @default(PENDING)
notes String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

guest Guest @relation(fields: [guestId], references: [id])
room Room @relation(fields: [roomId], references: [id])

@@index([guestId])
@@index([roomId])
@@index([checkIn, checkOut])
}

enum BookingStatus {
PENDING
CONFIRMED
CANCELLED
COMPLETED
}






Run the migration:




npx prisma migrate dev --name init
npx prisma generate









Setting Up Zod Schemas



Zod will validate our inputs at runtime. Create validation schemas that complement your Prisma models:




// src/schemas/booking.schema.ts
import { z } from 'zod';

export const createBookingSchema = z.object({
guestId: z.string().cuid(),
roomId: z.string().cuid(),
checkIn: z.string().datetime().or(z.date()),
checkOut: z.string().datetime().or(z.date()),
notes: z.string().optional(),
}).refine(
(data) => new Date(data.checkOut) > new Date(data.checkIn),
{
message: "Check-out date must be after check-in date",
path: ["checkOut"],
}
);

export const updateBookingSchema = z.object({
id: z.string().cuid(),
status: z.enum(['PENDING', 'CONFIRMED', 'CANCELLED', 'COMPLETED']).optional(),
notes: z.string().optional(),
});

export const getBookingsByDateRangeSchema = z.object({
startDate: z.string().datetime().or(z.date()),
endDate: z.string().datetime().or(z.date()),
roomId: z.string().cuid().optional(),
});

// Export inferred types
export type CreateBookingInput = z.infer<typeof createBookingSchema>;
export type UpdateBookingInput = z.infer<typeof updateBookingSchema>;
export type GetBookingsByDateRangeInput = z.infer<typeof getBookingsByDateRangeSchema>;









// src/schemas/guest.schema.ts
import { z } from 'zod';

export const createGuestSchema = z.object({
email: z.string().email(),
firstName: z.string().min(1),
lastName: z.string().min(1),
phone: z.string().optional(),
});

export const updateGuestSchema = z.object({
id: z.string().cuid(),
email: z.string().email().optional(),
firstName: z.string().min(1).optional(),
lastName: z.string().min(1).optional(),
phone: z.string().optional(),
});

export type CreateGuestInput = z.infer<typeof createGuestSchema>;
export type UpdateGuestInput = z.infer<typeof updateGuestSchema>;









Creating the tRPC Context



The context is where we'll inject dependencies like our Prisma client:




// src/trpc/context.ts
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient({
log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
});

export interface Context {
prisma: PrismaClient;
}

export const createContext = (): Context => {
return {
prisma,
};
};









Initializing tRPC



Set up the base tRPC configuration:




// src/trpc/trpc.ts
import { initTRPC } from '@trpc/server';
import { Context } from './context';

const t = initTRPC.context<Context>().create();

export const router = t.router;
export const publicProcedure = t.procedure;









Building Type-Safe Procedures



Now comes the powerful part—creating procedures that are fully type-safe:




// src/routers/booking.router.ts
import { router, publicProcedure } from '../trpc/trpc';
import { z } from 'zod';
import {
createBookingSchema,
updateBookingSchema,
getBookingsByDateRangeSchema,
} from '../schemas/booking.schema';
import { Decimal } from '@prisma/client/runtime/library';

export const bookingRouter = router({
create: publicProcedure
.input(createBookingSchema)
.mutation(async ({ ctx, input }) => {
const { checkIn, checkOut, roomId, guestId, notes } = input;

// Check room availability
const existingBookings = await ctx.prisma.booking.findMany({
where: {
roomId,
status: { in: ['PENDING', 'CONFIRMED'] },
OR: [
{
checkIn: { lte: new Date(checkOut) },
checkOut: { gte: new Date(checkIn) },
},
],
},
});

if (existingBookings.length > 0) {
throw new Error('Room is not available for the selected dates');
}

// Get room to calculate price
const room = await ctx.prisma.room.findUnique({
where: { id: roomId },
});

if (!room) {
throw new Error('Room not found');
}

// Calculate total price
const nights = Math.ceil(
(new Date(checkOut).getTime() - new Date(checkIn).getTime()) /
(1000 * 60 * 60 * 24)
);
const totalPrice = new Decimal(room.pricePerNight).times(nights);

// Create booking
return ctx.prisma.booking.create({
data: {
guestId,
roomId,
checkIn: new Date(checkIn),
checkOut: new Date(checkOut),
totalPrice,
notes,
status: 'PENDING',
},
include: {
guest: true,
room: true,
},
});
}),

update: publicProcedure
.input(updateBookingSchema)
.mutation(async ({ ctx, input }) => {
const { id, ...data } = input;

return ctx.prisma.booking.update({
where: { id },
data,
include: {
guest: true,
room: true,
},
});
}),

getById: publicProcedure
.input(z.object({ id: z.string().cuid() }))
.query(async ({ ctx, input }) => {
const booking = await ctx.prisma.booking.findUnique({
where: { id: input.id },
include: {
guest: true,
room: true,
},
});

if (!booking) {
throw new Error('Booking not found');
}

return booking;
}),

getByDateRange: publicProcedure
.input(getBookingsByDateRangeSchema)
.query(async ({ ctx, input }) => {
const { startDate, endDate, roomId } = input;

return ctx.prisma.booking.findMany({
where: {
...(roomId && { roomId }),
OR: [
{
checkIn: {
gte: new Date(startDate),
lte: new Date(endDate),
},
},
{
checkOut: {
gte: new Date(startDate),
lte: new Date(endDate),
},
},
],
},
include: {
guest: true,
room: true,
},
orderBy: {
checkIn: 'asc',
},
});
}),

cancel: publicProcedure
.input(z.object({ id: z.string().cuid() }))
.mutation(async ({ ctx, input }) => {
return ctx.prisma.booking.update({
where: { id: input.id },
data: { status: 'CANCELLED' },
include: {
guest: true,
room: true,
},
});
}),
});









// src/routers/guest.router.ts
import { router, publicProcedure } from '../trpc/trpc';
import { z } from 'zod';
import { createGuestSchema, updateGuestSchema } from '../schemas/guest.schema';

export const guestRouter = router({
create: publicProcedure
.input(createGuestSchema)
.mutation(async ({ ctx, input }) => {
return ctx.prisma.guest.create({
data: input,
});
}),

update: publicProcedure
.input(updateGuestSchema)
.mutation(async ({ ctx, input }) => {
const { id, ...data } = input;

return ctx.prisma.guest.update({
where: { id },
data,
});
}),

getById: publicProcedure
.input(z.object({ id: z.string().cuid() }))
.query(async ({ ctx, input }) => {
const guest = await ctx.prisma.guest.findUnique({
where: { id: input.id },
include: {
bookings: {
include: {
room: true,
},
orderBy: {
checkIn: 'desc',
},
},
},
});

if (!guest) {
throw new Error('Guest not found');
}

return guest;
}),

getByEmail: publicProcedure
.input(z.object({ email: z.string().email() }))
.query(async ({ ctx, input }) => {
return ctx.prisma.guest.findUnique({
where: { email: input.email },
include: {
bookings: {
include: {
room: true,
},
},
},
});
}),

list: publicProcedure
.input(
z.object({
limit: z.number().min(1).max(100).default(10),
cursor: z.string().cuid().optional(),
})
)
.query(async ({ ctx, input }) => {
const { limit, cursor } = input;

const guests = await ctx.prisma.guest.findMany({
take: limit + 1,
cursor: cursor ? { id: cursor } : undefined,
orderBy: {
createdAt: 'desc',
},
});

let nextCursor: string | undefined = undefined;
if (guests.length > limit) {
const nextItem = guests.pop();
nextCursor = nextItem?.id;
}

return {
guests,
nextCursor,
};
}),
});









Creating the App Router



Combine all routers into a single app router:




// src/routers/index.ts
import { router } from '../trpc/trpc';
import { bookingRouter } from './booking.router';
import { guestRouter } from './guest.router';

export const appRouter = router({
booking: bookingRouter,
guest: guestRouter,
});

export type AppRouter = typeof appRouter;









Setting Up the Server



Now let's create a server using Fastify (or Express if you prefer):




// src/server.ts
import Fastify from 'fastify';
import cors from '@fastify/cors';
import { fastifyTRPCPlugin } from '@trpc/server/adapters/fastify';
import { appRouter } from './routers';
import { createContext } from './trpc/context';

const server = Fastify({
logger: true,
maxParamLength: 5000,
});

// Enable CORS
server.register(cors, {
origin: process.env.FRONTEND_URL || 'http://localhost:5173',
credentials: true,
});

// Register tRPC
server.register(fastifyTRPCPlugin, {
prefix: '/trpc',
trpcOptions: {
router: appRouter,
createContext,
onError({ path, error }) {
console.error(`Error in tRPC handler on path '${path}':`, error);
},
},
});

const start = async () => {
try {
const port = Number(process.env.PORT) || 3000;
await server.listen({ port, host: '0.0.0.0' });
console.log(`🚀 Server ready at http://localhost:${port}`);
} catch (err) {
server.log.error(err);
process.exit(1);
}
};

start();






Don't forget to install Fastify:




npm install fastify @fastify/cors @trpc/server









Frontend Integration (SvelteKit Example)



Now for the magic—using your type-safe API in the frontend:




// src/lib/trpc.ts (in your SvelteKit app)
import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from '../../../backend/src/routers';

export const trpc = createTRPCProxyClient<AppRouter>({
links: [
httpBatchLink({
url: 'http://localhost:3000/trpc',
}),
],
});









<!-- src/routes/bookings/+page.svelte -->
<script lang="ts">
import { trpc } from '$lib/trpc';
import { onMount } from 'svelte';

let bookings: Awaited<ReturnType<typeof trpc.booking.getByDateRange.query>> = [];
let loading = true;

onMount(async () => {
try {
const today = new Date();
const nextMonth = new Date();
nextMonth.setMonth(nextMonth.getMonth() + 1);

bookings = await trpc.booking.getByDateRange.query({
startDate: today.toISOString(),
endDate: nextMonth.toISOString(),
});
} catch (error) {
console.error('Failed to load bookings:', error);
} finally {
loading = false;
}
});

async function createBooking() {
try {
const newBooking = await trpc.booking.create.mutate({
guestId: 'clxxx...', // example ID
roomId: 'clyyy...', // example ID
checkIn: new Date('2024-03-15').toISOString(),
checkOut: new Date('2024-03-18').toISOString(),
notes: 'Early check-in requested',
});

console.log('Booking created:', newBooking);
// TypeScript knows the exact shape of newBooking!
} catch (error) {
console.error('Failed to create booking:', error);
}
}
</script>

{#if loading}
<p>Loading bookings...</p>
{:else}
<div class="bookings">
{#each bookings as booking}
<div class="booking-card">
<h3>{booking.guest.firstName} {booking.guest.lastName}</h3>
<p>Room: {booking.room.name}</p>
<p>Check-in: {new Date(booking.checkIn).toLocaleDateString()}</p>
<p>Check-out: {new Date(booking.checkOut).toLocaleDateString()}</p>
<p>Status: {booking.status}</p>
</div>
{/each}
</div>
{/if}









The Benefits You Get






1. Compile-Time Type Safety



If you try to call a procedure that doesn't exist or pass wrong parameters, TypeScript will catch it immediately:




// ❌ TypeScript error: Property 'nonExistent' does not exist
await trpc.booking.nonExistent.query();

// ❌ TypeScript error: Type 'number' is not assignable to type 'string'
await trpc.booking.getById.query({ id: 123 });

// ✅ Correct
await trpc.booking.getById.query({ id: 'clxxx...' });









2. Runtime Validation



Zod ensures that even if something bypasses TypeScript (like user input), your data is validated:




// If checkOut is before checkIn, Zod will throw an error at runtime
await trpc.booking.create.mutate({
guestId: 'clxxx...',
roomId: 'clyyy...',
checkIn: '2024-03-18',
checkOut: '2024-03-15', // ❌ Will fail validation
});









3. Auto-Complete Everywhere



Your IDE will provide intelligent autocomplete for all procedures, inputs, and return types:




const booking = await trpc.booking.getById.query({ id: 'clxxx...' });

// Your IDE knows all available properties:
booking.guest.firstName; // ✅
booking.room.name; // ✅
booking.totalPrice; // ✅
booking.nonExistent; // ❌ TypeScript error









4. Refactoring Safety



If you change your database schema or API, TypeScript will immediately show you everywhere in your frontend that needs updating. No more runtime surprises!






Advanced Patterns






Middleware for Authentication






// src/trpc/trpc.ts
import { initTRPC, TRPCError } from '@trpc/server';
import { Context } from './context';

const t = initTRPC.context<Context>().create();

const isAuthed = t.middleware(({ ctx, next }) => {
// Check if user is authenticated (example)
if (!ctx.session?.user) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}

return next({
ctx: {
...ctx,
user: ctx.session.user,
},
});
});

export const router = t.router;
export const publicProcedure = t.procedure;
export const protectedProcedure = t.procedure.use(isAuthed);









Input Transformers






export const createBookingSchema = z.object({
guestId: z.string().cuid(),
roomId: z.string().cuid(),
checkIn: z.string().datetime(),
checkOut: z.string().datetime(),
notes: z.string().optional(),
}).transform((data) => ({
...data,
checkIn: new Date(data.checkIn),
checkOut: new Date(data.checkOut),
}));









Optimistic Updates






// In your SvelteKit component
import { writable } from 'svelte/store';

const bookingsStore = writable<Booking[]>([]);

async function cancelBooking(bookingId: string) {
// Optimistic update
bookingsStore.update(bookings =>
bookings.map(b => b.id === bookingId
? { ...b, status: 'CANCELLED' }
: b
)
);

try {
await trpc.booking.cancel.mutate({ id: bookingId });
} catch (error) {
// Revert on error
const freshBookings = await trpc.booking.getByDateRange.query({
startDate: new Date().toISOString(),
endDate: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
});
bookingsStore.set(freshBookings);
}
}









Testing Your Type-Safe API






// src/routers/__tests__/booking.test.ts
import { appRouter } from '../index';
import { createContext } from '../../trpc/context';

describe('Booking Router', () => {
const ctx = createContext();
const caller = appRouter.createCaller(ctx);

it('should create a booking', async () => {
const booking = await caller.booking.create({
guestId: 'test-guest-id',
roomId: 'test-room-id',
checkIn: new Date('2024-03-15').toISOString(),
checkOut: new Date('2024-03-18').toISOString(),
});

expect(booking).toHaveProperty('id');
expect(booking.status).toBe('PENDING');
});

it('should reject overlapping bookings', async () => {
await expect(
caller.booking.create({
guestId: 'test-guest-id',
roomId: 'test-room-id',
checkIn: new Date('2024-03-16').toISOString(),
checkOut: new Date('2024-03-17').toISOString(),
})
).rejects.toThrow('Room is not available');
});
});









Performance Considerations






Database Query Optimization






// Use Prisma's select to fetch only needed fields
getById: publicProcedure
.input(z.object({ id: z.string().cuid() }))
.query(async ({ ctx, input }) => {
return ctx.prisma.booking.findUnique({
where: { id: input.id },
select: {
id: true,
checkIn: true,
checkOut: true,
status: true,
guest: {
select: {
firstName: true,
lastName: true,
email: true,
},
},
room: {
select: {
name: true,
pricePerNight: true,
},
},
},
});
}),









Request Batching



tRPC automatically batches requests made within a short time window:




// These three requests will be batched into a single HTTP request
const [booking1, booking2, guest1] = await Promise.all([
trpc.booking.getById.query({ id: 'id1' }),
trpc.booking.getById.query({ id: 'id2' }),
trpc.guest.getById.query({ id: 'guest1' }),
]);









Deployment Considerations






Environment Variables






# .env
DATABASE_URL="postgresql://user:password@localhost:5432/booking_db"
PORT=3000
NODE_ENV=production
FRONTEND_URL=https://your-frontend.com









Docker Setup






# Dockerfile
FROM node:20-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY prisma ./prisma
RUN npx prisma generate

COPY . .
RUN npm run build

EXPOSE 3000

CMD ["npm", "start"]









Conclusion



Building a type-safe backend with tRPC, Zod, and Prisma provides incredible developer experience and confidence. You get:





  • End-to-end type safety from database to frontend


  • Runtime validation that catches errors before they reach your database


  • Excellent IDE support with autocomplete and inline documentation


  • Refactoring confidence knowing TypeScript will catch breaking changes


  • No code generation needed—types are inferred automatically



The initial setup requires some learning, but the productivity gains and reduced bugs make it worthwhile for any serious TypeScript project. The combination of compile-time type checking with runtime validation gives you the best of both worlds.



This stack provides a solid foundation that scales well and keeps your codebase maintainable, whether you're building a booking system, an e-commerce platform, or any other API-driven application.






Resources





Have questions or want to share your experience with this stack? Drop a comment below!

1. Sofort-Triage & Abwehrmaßnahmen

SOC Incident Playbook: Remote Code Execution (RCE) Defense
Syntax validiert (0 Fehler)
title: Detect Exploitation - Building a Type-Safe Backend with tRPC, Zod, and Prisma
id: 84cf3c5e-f52b-4b0d-80d8-61c933e348b6
status: experimental
description: Automatisch generierte SIEM-Erkennungsregel basierend auf CTI Intelligence
references:
  - https://tsecurity.de/
author: iShareStuff CTI Automated Detection Engine
date: 2026-09-25
logsource:
  category: network_connection
  product: any
detection:
  selection:
      CommandLine|contains:
        - 'exploit'
  condition: selection
falsepositives:
  - Legitime administrative Zugriffe oder Penetrationstests
level: high
tags:
  - attack.initial_access
Syntax validiert (0 Fehler)
rule CTI_Threat_Indicator {
    meta:
        author = "iShareStuff CTI Automated Detection Engine"
        date = "2026-09-25"
        description = "YARA Signature for "
    strings:
        $str = "Building a Type-Safe Backend w" ascii wide
    condition:
        any of them
}
Syntax validiert (0 Fehler)
index=security sourcetype IN ("cisco:asa", "pan:traffic", "zeek_conn", "suricata", "WinEventLog:Security")
("Building a Type-Safe Backend with tRPC Z")
| stats count earliest(_time) as first_seen latest(_time) as last_seen by src_ip, dest_ip, dest_host, signature
| eval first_seen=strftime(first_seen, "%Y-%m-%d %H:%M:%S"), last_seen=strftime(last_seen, "%Y-%m-%d %H:%M:%S")
| sort - count
Syntax validiert (0 Fehler)
message: "*Building a Type-Safe Backend with tRPC Z*"
Syntax validiert (0 Fehler)
CommonSecurityLog
| where Message has "Building a Type-Safe Backend with tRPC Z"
| summarize EventCount = count(), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated) by SourceIP, DestinationIP, DestinationPort, Activity
| extend DetectionRule = "iShareStuff-CTI-Compiled"
| sort by EventCount desc

2. Cyber Threat Intelligence & Forensik

🎯
MITRE ATT&CK Matrix Navigator 14 Taktiken
Reconnaissance
-
Resource Development
-
Initial Access
Execution
Persistence
-
Privilege Escalation
Defense Evasion
Credential Access
-
Discovery
-
Lateral Movement
-
Collection
-
Command and Control
Exfiltration
-
Impact
tsecurity.de Cognitive Threat RAG
Fokus-Vektor:

Kognitive Analyse für identifizierte Bedrohung: Erhöhte Bedrohungslage im Bereich Building a Type-Safe Backend with tRPC, .... Basierend auf 368k Vektor-Korrelationen werden sofortige Isolationsmaßnahmen für betroffene Endpunkte empfohlen.

🛡️ Angriffsfläche & Exposure

Netzwerk/Remote-Zugriff ohne Vorauthentifizierung möglich.

⚡ Empfohlene Sofortmaßnahmen
  • 1. Perimeter-Inspektion: Relevante Portfreigaben und exponierte Endpunkte unverzüglich scannen.
  • 2. Patch-Applikation: Hersteller-Hotfix einspielen oder betroffene Daemons in isolierte DMZ-Segmente überführen.
  • 3. Telemetrie & EDR-Alerts: Prozessaufrufe und Child-Processes auf anomale Shell-Spawns überwachen.
🔗 Semantisch verwandte Zero-Days MariaDB 11.7 VEC
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Building a Type-Safe Backend with tRPC, Zod, and Prisma

Thematisch verwandte Begriffe: Building, TypeSafe, Backend, with · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-97898 | Insecure Direct Object Reference / missing object-level authorization in…
Advisory →
tsecurity.de Icon
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag