Horse provides a flexible session management system that allows you to store and retrieve custom data objects across multiple requests. The session system is type-safe and integrated directly into the request object.
The session system in Horse consists of two main components:
THorseSessions: A session container that stores multiple session objects by their class type
TSession: Base class for creating custom session objects
Sessions in Horse are stored per-request and must be managed by middleware or your application logic. Horse does not provide automatic persistence or expiration - you’ll need to implement this using a middleware or external session store.
You can set session data using the Sessions.SetSession method:
THorse.Get('/login', procedure(Req: THorseRequest; Res: THorseResponse; Next: TNextProc) var LUserSession: TUserSession; begin LUserSession := TUserSession.Create; LUserSession.UserId := 123; LUserSession.Username := 'john.doe'; LUserSession.IsAuthenticated := True; // Store session in the request Req.Sessions.SetSession(TUserSession, LUserSession); Res.Send('User logged in successfully'); end);
The THorseSessions container owns the session objects. When you call SetSession, ownership is transferred to the container, which will free the object when appropriate. Do not manually free session objects after adding them.
THorse.Get('/profile', procedure(Req: THorseRequest; Res: THorseResponse; Next: TNextProc) var LUserSession: TUserSession; begin if Req.Sessions.TryGetSession<TUserSession>(LUserSession) then begin if LUserSession.IsAuthenticated then Res.Send('Welcome, ' + LUserSession.Username) else Res.Status(THTTPStatus.Unauthorized).Send('Not authenticated'); end else Res.Status(THTTPStatus.Unauthorized).Send('No session found'); end);
THorse.Get('/user-data', procedure(Req: THorseRequest; Res: THorseResponse; Next: TNextProc) var LSession: TSession; LUserSession: TUserSession; begin if Req.Sessions.Contains(TUserSession) then begin LSession := Req.Sessions.Session[TUserSession]; LUserSession := TUserSession(LSession); Res.Send('User ID: ' + LUserSession.UserId.ToString); end else Res.Status(THTTPStatus.NotFound).Send('Session not found'); end);
A common pattern is to create middleware that loads sessions from a persistent store:
uses Horse, Horse.Session;type TUserSession = class(TSession) private FUserId: Integer; FToken: string; public property UserId: Integer read FUserId write FUserId; property Token: string read FToken write FToken; end;procedure SessionMiddleware(Req: THorseRequest; Res: THorseResponse; Next: TNextProc);var LToken: string; LUserSession: TUserSession; LUserId: Integer;begin // Get token from header LToken := Req.Headers['Authorization']; if LToken <> '' then begin // Validate token and get user ID from your authentication system if ValidateTokenAndGetUserId(LToken, LUserId) then begin LUserSession := TUserSession.Create; LUserSession.UserId := LUserId; LUserSession.Token := LToken; Req.Sessions.SetSession(TUserSession, LUserSession); end; end; Next();end;begin // Apply middleware globally THorse.Use(SessionMiddleware); // Protected route THorse.Get('/api/user/profile', procedure(Req: THorseRequest; Res: THorseResponse; Next: TNextProc) var LUserSession: TUserSession; begin if not Req.Sessions.TryGetSession<TUserSession>(LUserSession) then begin Res.Status(THTTPStatus.Unauthorized).Send('Unauthorized'); Exit; end; // User is authenticated, proceed with request Res.Send('Profile for user: ' + LUserSession.UserId.ToString); end); THorse.Listen(9000);end.
unit MyApp.Auth;interfaceuses Horse, Horse.Session;type TUserSession = class(TSession) private FUserId: Integer; FEmail: string; FRoles: TArray<string>; public property UserId: Integer read FUserId write FUserId; property Email: string read FEmail write FEmail; property Roles: TArray<string> read FRoles write FRoles; function HasRole(const ARole: string): Boolean; end;function GetCurrentUser(AReq: THorseRequest; out AUser: TUserSession): Boolean;function RequireAuth(AReq: THorseRequest; ARes: THorseResponse): Boolean;function RequireRole(AReq: THorseRequest; ARes: THorseResponse; const ARole: string): Boolean;implementationfunction TUserSession.HasRole(const ARole: string): Boolean;var LRole: string;begin Result := False; for LRole in FRoles do begin if LRole = ARole then Exit(True); end;end;function GetCurrentUser(AReq: THorseRequest; out AUser: TUserSession): Boolean;begin Result := AReq.Sessions.TryGetSession<TUserSession>(AUser);end;function RequireAuth(AReq: THorseRequest; ARes: THorseResponse): Boolean;var LUser: TUserSession;begin Result := GetCurrentUser(AReq, LUser); if not Result then ARes.Status(THTTPStatus.Unauthorized).Send('Authentication required');end;function RequireRole(AReq: THorseRequest; ARes: THorseResponse; const ARole: string): Boolean;var LUser: TUserSession;begin if not GetCurrentUser(AReq, LUser) then begin ARes.Status(THTTPStatus.Unauthorized).Send('Authentication required'); Exit(False); end; Result := LUser.HasRole(ARole); if not Result then ARes.Status(THTTPStatus.Forbidden).Send('Insufficient permissions');end;end.
Usage:
THorse.Get('/admin/users', procedure(Req: THorseRequest; Res: THorseResponse; Next: TNextProc) begin if not RequireRole(Req, Res, 'admin') then Exit; // User is authenticated and has admin role Res.Send('Admin users list'); end);
Always use TryGetSession when you’re not sure if a session exists. This prevents exceptions and makes your code more robust.
2
Create session middleware
Implement a middleware to load sessions at the beginning of each request, making session data available to all subsequent handlers.
3
Don't store large objects
Keep session objects lightweight. Store only essential data like user IDs and tokens, not entire datasets.
4
Implement session persistence
Use external storage (Redis, database, etc.) for session persistence across server restarts and load-balanced environments.
For production applications, consider implementing a session store using Redis or a database to persist session data between requests and across server instances.