Recommended Free Tools
iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more
A practical design is to make PostgreSQL the authoritative store for messages and their expiration timestamps, use Prisma to access PostgreSQL, and use Redis only as a short-lived cache. NestJS validates every request before it reaches the persistence layer. This example implements time-based expiration and reusable links; it does not make links one-time-read. The expiration limits, cache policy, and deletion promise are product choices you must define for your service.
Choose the expiration and storage rules first
Before writing endpoints, decide what “temporary” means. The implementation below uses an absolute expiration time: a message can be retrieved repeatedly until its expiration time, and access does not extend its lifetime. When the expiration time has passed, the API returns not found. One-time retrieval is a different policy and is not included.
Use PostgreSQL as the source of truth. Store the message and its expiration timestamp there; Redis is an optional cache of the message payload, not the authority on whether the message is still available. This avoids treating Redis key expiry as proof that PostgreSQL, logs, or backups no longer contain a copy.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
- Set a maximum content size and maximum allowed lifetime as explicit product configuration. The appropriate values depend on your use case; neither NestJS, Prisma, nor Redis sets them for this API.
- Choose whether the client supplies a duration or an absolute timestamp. A duration is usually simpler to validate: the server calculates the expiration time when it creates the message.
- Decide how long expired rows remain in PostgreSQL and how they are eventually removed. Expiration checks at read time and physical deletion are separate operations.
- Document whether deletion covers only the live database and cache or also logs and backups. Do not promise complete erasure unless the retention and backup processes support it.
Define the API contract
One minimal contract creates a message and returns an opaque identifier with an expiration time. A subsequent request retrieves the message by identifier. The identifier is a locator, not an authorization system; define controls against guessing, abuse, and unwanted exposure before treating a link as private.
#1 Best Overall
| Operation | Example route | Behavior |
|---|---|---|
| Create | POST /messages |
Validate content and requested lifetime; save the message and return its identifier and expiration timestamp. |
| Retrieve | GET /messages/:id |
Return the message only if it exists and has not expired; otherwise return not found. |
Choose the actual response status codes, error shape, and identifier format as part of the API contract. Make expiry behavior consistent for absent and expired messages if you do not want the endpoint to reveal whether an expired identifier once existed.
Set up the NestJS request boundary
NestJS recommends validating every piece of data an application receives before acting on it. Its ValidationPipe works with concrete DTO classes decorated with validation rules; TypeScript interfaces and generics do not retain the runtime metadata that class-based validation needs. Register the pipe globally when the same policy should apply across the API, or attach it to selected routes. Pin your NestJS and validation-library versions, then confirm the imports and options against their matching documentation.
import { ValidationPipe } from '@nestjs/common';
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}),
);
Define allowed lifetime bounds in configuration and validate against them rather than embedding an undocumented limit in a DTO. For illustration, a DTO can validate basic types first; a service-level policy check can then compare the requested duration with configured bounds.
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteimport { IsInt, IsString, Min } from 'class-validator';
export class CreateMessageDto {
@IsString()
content!: string;
@IsInt()
@Min(1)
expiresInSeconds!: number;
}
This DTO checks that content is a string and the duration is a positive integer; it does not enforce a content-size limit or your maximum lifetime. Add those rules using limits chosen for your product. Validate route identifiers too—for example, NestJS provides ParseUUIDPipe if the API uses UUIDs. Validation does not itself provide rate limiting or abuse protection.
Rank #2
Model messages in PostgreSQL with Prisma
A simple model needs an identifier, content, and an absolute expiration timestamp. Add ownership, status, or audit fields only if the product requires them. The following schema is illustrative; verify field types and migration behavior against the Prisma ORM major version you pin.
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model Message {
id String @id @default(uuid()) @db.Uuid
content String
expiresAt DateTime @db.Timestamptz(3)
createdAt DateTime @default(now()) @db.Timestamptz(3)
@@index([expiresAt])
}
Configure the PostgreSQL connection string for the environment where the application runs. Prisma documents using a pooled runtime URL and a direct URL for CLI operations in serverless PostgreSQL deployments; confirm the configuration for your provider and Prisma version rather than applying that pattern to every hosting setup.
When creation involves multiple PostgreSQL writes that must succeed or fail together—for example, creating a message and a related metadata record—use the transaction API for your selected Prisma version. A database transaction can make those database writes atomic. It does not include Redis, so it cannot make a PostgreSQL write and a Redis operation commit as one transaction.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Fix the driver behind crashes, sound loss and screen glitches3Repair Windows errors before they cause bigger problemsCreate messages with a server-calculated expiration
Compute expiresAt on the server from the current time and the validated duration. This keeps clients from choosing an arbitrary absolute timestamp and makes the start of the lifetime unambiguous. Reject a duration outside the configured product bounds before saving.
Rank #3
async create(dto: CreateMessageDto) {
this.expirationPolicy.assertAllowed(dto.expiresInSeconds);
const expiresAt = new Date(
Date.now() + dto.expiresInSeconds * 1000,
);
const message = await this.prisma.message.create({
data: {
content: dto.content,
expiresAt,
},
select: { id: true, expiresAt: true },
});
return message;
}
The policy service should reject non-finite values and durations above the configured maximum. The DTO catches common type errors, but a runtime policy check remains useful because configuration and validation are separate concerns.
Only after PostgreSQL confirms creation should the service attempt to populate a Redis cache. If the cache write fails, the message still exists in PostgreSQL; the API can return the successful creation result while recording the cache failure according to its operational policy.
Retrieve without letting Redis decide validity
On a read, check PostgreSQL for the message and its expiration timestamp. If no row exists, or the timestamp is at or before the server’s current time, return the chosen not-found response. Do not serve a cached payload merely because Redis still has a key: cache entries can be stale, and their TTL is not the application’s authoritative expiry check.
async findActive(id: string) {
const message = await this.prisma.message.findUnique({
where: { id },
});
if (!message || message.expiresAt.getTime() <= Date.now()) {
throw new NotFoundException();
}
return {
id: message.id,
content: message.content,
expiresAt: message.expiresAt,
};
}
This read path treats PostgreSQL as authoritative even if Redis is unavailable. If you later use Redis to avoid a database read on cache hits, you are changing the consistency model: decide how stale content is prevented, how PostgreSQL expiry or deletion invalidates cache entries, and what happens during a database outage. Do not claim the cache and database are atomically consistent.
Use Redis TTL as cache cleanup, not as the deletion promise
Redis can automatically expire a cached key. Set its lifetime from the remaining time until the PostgreSQL expiresAt, not from the original requested duration if time has already elapsed. Skip caching if the remaining lifetime is zero or negative. This is cache housekeeping; the application must still enforce expiry against PostgreSQL.
const remainingSeconds = Math.floor(
(message.expiresAt.getTime() - Date.now()) / 1000,
);
if (remainingSeconds > 0) {
await redis.set(
`message:${message.id}`,
JSON.stringify(message),
{ EX: remainingSeconds },
);
}
Redis documents EXPIRE key seconds, expiration options on SET, and TTL for inspecting remaining lifetime. TTL returns -1 when a key has no expiry and -2 when it is missing. A plain SET that overwrites a key clears its existing expiry unless the write supplies a new expiration or uses KEEPTTL. Therefore, every cache update path must preserve or reset the intended TTL.
Expiry destroys the Redis key according to Redis’s TTL behavior, but it says nothing by itself about copies in PostgreSQL, application logs, exports, or backups. If you need physical cleanup of expired PostgreSQL rows, implement and monitor a separate deletion process, and define the relevant retention periods.
Keep failures and operations explicit
PostgreSQL and Redis are separate systems. Choose failure behavior rather than assuming one operation covers both.
- PostgreSQL unavailable on create: fail creation; without a successful database write, do not report a message as stored.
- Redis unavailable on create: if Redis is only a cache, creation can still succeed after PostgreSQL commits. The cache can be populated on a later read.
- Redis unavailable on read: read from PostgreSQL, which remains authoritative.
- Expired row still present: reject retrieval based on
expiresAt; physical removal can occur asynchronously under the chosen retention policy. - Cache key has no TTL: treat this as a cache-policy defect, not evidence that a message has become valid indefinitely. Rebuild or remove the key according to the authoritative row.
For observability, record operational failures without logging message content or link secrets. Decide how to redact identifiers and request bodies, add abuse controls appropriate to the service, and specify whether content is encrypted in transit and at rest in your chosen deployment. Expiration alone does not make a message confidential or secure.
Deployment and version checks
Pin the Prisma ORM version before adopting setup commands, schema configuration, or transaction examples. Prisma’s NestJS integration and PostgreSQL connector describe the framework/database path, but configuration and APIs can differ across major versions. For a serverless PostgreSQL deployment, verify whether your runtime needs a pooled connection and your CLI needs a direct connection. Also verify the Redis client’s syntax for setting expiration options, since client APIs are version-specific.
Before release, test the contract at the boundaries: malformed bodies, rejected lifetime values, successful create and retrieve, retrieval after expiration, absent identifiers, PostgreSQL failure, Redis failure, and cache writes that accidentally omit expiration. Test that the configured cleanup process does not substitute for the read-time expiration check.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

