saas-backend-kit 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/PUBLISHING.md +133 -0
  3. package/README.md +459 -0
  4. package/copy-dts.js +255 -0
  5. package/dist/auth/index.d.ts +58 -0
  6. package/dist/auth/index.js +584 -0
  7. package/dist/auth/index.js.map +1 -0
  8. package/dist/auth/index.mjs +569 -0
  9. package/dist/auth/index.mjs.map +1 -0
  10. package/dist/config/index.d.ts +22 -0
  11. package/dist/config/index.js +106 -0
  12. package/dist/config/index.js.map +1 -0
  13. package/dist/config/index.mjs +100 -0
  14. package/dist/config/index.mjs.map +1 -0
  15. package/dist/index.d.ts +25 -0
  16. package/dist/index.js +1303 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/index.mjs +1281 -0
  19. package/dist/index.mjs.map +1 -0
  20. package/dist/logger/index.d.ts +18 -0
  21. package/dist/logger/index.js +188 -0
  22. package/dist/logger/index.js.map +1 -0
  23. package/dist/logger/index.mjs +178 -0
  24. package/dist/logger/index.mjs.map +1 -0
  25. package/dist/notifications/index.d.ts +35 -0
  26. package/dist/notifications/index.js +339 -0
  27. package/dist/notifications/index.js.map +1 -0
  28. package/dist/notifications/index.mjs +328 -0
  29. package/dist/notifications/index.mjs.map +1 -0
  30. package/dist/queue/index.d.ts +33 -0
  31. package/dist/queue/index.js +306 -0
  32. package/dist/queue/index.js.map +1 -0
  33. package/dist/queue/index.mjs +293 -0
  34. package/dist/queue/index.mjs.map +1 -0
  35. package/dist/rate-limit/index.d.ts +11 -0
  36. package/dist/rate-limit/index.js +290 -0
  37. package/dist/rate-limit/index.js.map +1 -0
  38. package/dist/rate-limit/index.mjs +286 -0
  39. package/dist/rate-limit/index.mjs.map +1 -0
  40. package/dist/response/index.d.ts +29 -0
  41. package/dist/response/index.js +120 -0
  42. package/dist/response/index.js.map +1 -0
  43. package/dist/response/index.mjs +114 -0
  44. package/dist/response/index.mjs.map +1 -0
  45. package/examples/express/.env.example +41 -0
  46. package/examples/express/app.ts +203 -0
  47. package/package.json +109 -0
  48. package/src/auth/express.ts +250 -0
  49. package/src/auth/fastify.ts +65 -0
  50. package/src/auth/index.ts +6 -0
  51. package/src/auth/jwt.ts +47 -0
  52. package/src/auth/oauth.ts +117 -0
  53. package/src/auth/rbac.ts +82 -0
  54. package/src/auth/types.ts +69 -0
  55. package/src/config/index.ts +120 -0
  56. package/src/index.ts +16 -0
  57. package/src/logger/index.ts +110 -0
  58. package/src/notifications/index.ts +262 -0
  59. package/src/plugin.ts +192 -0
  60. package/src/queue/index.ts +208 -0
  61. package/src/rate-limit/express.ts +144 -0
  62. package/src/rate-limit/fastify.ts +47 -0
  63. package/src/rate-limit/index.ts +2 -0
  64. package/src/response/index.ts +197 -0
  65. package/src/utils/index.ts +180 -0
  66. package/tsconfig.json +30 -0
  67. package/tsup.config.ts +24 -0
@@ -0,0 +1,47 @@
1
+ import jwt from 'jsonwebtoken';
2
+ import { JWTPayload, TokenPair } from './types';
3
+ import { config } from '../config';
4
+
5
+ export class JWTService {
6
+ private secret: string;
7
+ private expiresIn: string;
8
+ private refreshSecret: string;
9
+ private refreshExpiresIn: string;
10
+
11
+ constructor(options: { jwtSecret?: string; jwtExpiresIn?: string; refreshSecret?: string; refreshExpiresIn?: string } = {}) {
12
+ this.secret = options.jwtSecret || config.get('JWT_SECRET') || 'default-secret-change-in-production';
13
+ this.expiresIn = options.jwtExpiresIn || config.get('JWT_EXPIRES_IN') || '7d';
14
+ this.refreshSecret = options.refreshSecret || config.get('JWT_REFRESH_SECRET') || this.secret;
15
+ this.refreshExpiresIn = options.refreshExpiresIn || config.get('JWT_REFRESH_EXPIRES_IN') || '30d';
16
+ }
17
+
18
+ generateToken(payload: JWTPayload): string {
19
+ return jwt.sign(payload, this.secret, { expiresIn: this.expiresIn });
20
+ }
21
+
22
+ generateRefreshToken(payload: JWTPayload): string {
23
+ return jwt.sign(payload, this.refreshSecret, { expiresIn: this.refreshExpiresIn });
24
+ }
25
+
26
+ generateTokenPair(payload: JWTPayload): TokenPair {
27
+ return {
28
+ accessToken: this.generateToken(payload),
29
+ refreshToken: this.generateRefreshToken(payload),
30
+ };
31
+ }
32
+
33
+ verifyToken(token: string): JWTPayload {
34
+ return jwt.verify(token, this.secret) as JWTPayload;
35
+ }
36
+
37
+ verifyRefreshToken(token: string): JWTPayload {
38
+ return jwt.verify(token, this.refreshSecret) as JWTPayload;
39
+ }
40
+
41
+ refreshTokens(refreshToken: string): TokenPair {
42
+ const payload = this.verifyRefreshToken(refreshToken);
43
+ return this.generateTokenPair(payload);
44
+ }
45
+ }
46
+
47
+ export const jwtService = new JWTService();
@@ -0,0 +1,117 @@
1
+ import { OAuthProvider } from './types';
2
+ import { config } from '../config';
3
+
4
+ export interface OAuthUserInfo {
5
+ id: string;
6
+ email: string;
7
+ name?: string;
8
+ picture?: string;
9
+ }
10
+
11
+ export class OAuthService {
12
+ private providers: Map<string, OAuthProvider> = new Map();
13
+
14
+ constructor() {
15
+ const googleClientId = config.get('GOOGLE_CLIENT_ID');
16
+ const googleClientSecret = config.get('GOOGLE_CLIENT_SECRET');
17
+ const googleRedirectUri = config.get('GOOGLE_REDIRECT_URI');
18
+
19
+ if (googleClientId && googleClientSecret && googleRedirectUri) {
20
+ this.registerProvider('google', {
21
+ name: 'google',
22
+ clientId: googleClientId,
23
+ clientSecret: googleClientSecret,
24
+ redirectUri: googleRedirectUri,
25
+ authorizationUrl: 'https://accounts.google.com/o/oauth2/v2/auth',
26
+ tokenUrl: 'https://oauth2.googleapis.com/token',
27
+ userInfoUrl: 'https://www.googleapis.com/oauth2/v2/userinfo',
28
+ });
29
+ }
30
+ }
31
+
32
+ registerProvider(name: string, provider: OAuthProvider): void {
33
+ this.providers.set(name, provider);
34
+ }
35
+
36
+ getProvider(name: string): OAuthProvider | undefined {
37
+ return this.providers.get(name);
38
+ }
39
+
40
+ getAuthorizationUrl(providerName: string, state?: string): string {
41
+ const provider = this.getProvider(providerName);
42
+ if (!provider) {
43
+ throw new Error(`OAuth provider ${providerName} not registered`);
44
+ }
45
+
46
+ const params = new URLSearchParams({
47
+ client_id: provider.clientId,
48
+ redirect_uri: provider.redirectUri,
49
+ response_type: 'code',
50
+ scope: 'openid email profile',
51
+ state: state || '',
52
+ });
53
+
54
+ return `${provider.authorizationUrl}?${params.toString()}`;
55
+ }
56
+
57
+ async exchangeCode(providerName: string, code: string): Promise<{ accessToken: string; refreshToken?: string }> {
58
+ const provider = this.getProvider(providerName);
59
+ if (!provider) {
60
+ throw new Error(`OAuth provider ${providerName} not registered`);
61
+ }
62
+
63
+ const response = await fetch(provider.tokenUrl, {
64
+ method: 'POST',
65
+ headers: {
66
+ 'Content-Type': 'application/x-www-form-urlencoded',
67
+ },
68
+ body: new URLSearchParams({
69
+ client_id: provider.clientId,
70
+ client_secret: provider.clientSecret,
71
+ code,
72
+ grant_type: 'authorization_code',
73
+ redirect_uri: provider.redirectUri,
74
+ }),
75
+ });
76
+
77
+ if (!response.ok) {
78
+ const error = await response.text();
79
+ throw new Error(`OAuth token exchange failed: ${error}`);
80
+ }
81
+
82
+ const data = await response.json();
83
+ return {
84
+ accessToken: data.access_token,
85
+ refreshToken: data.refresh_token,
86
+ };
87
+ }
88
+
89
+ async getUserInfo(providerName: string, accessToken: string): Promise<OAuthUserInfo> {
90
+ const provider = this.getProvider(providerName);
91
+ if (!provider) {
92
+ throw new Error(`OAuth provider ${providerName} not registered`);
93
+ }
94
+
95
+ const response = await fetch(provider.userInfoUrl, {
96
+ headers: {
97
+ Authorization: `Bearer ${accessToken}`,
98
+ },
99
+ });
100
+
101
+ if (!response.ok) {
102
+ const error = await response.text();
103
+ throw new Error(`OAuth user info fetch failed: ${error}`);
104
+ }
105
+
106
+ const data = await response.json();
107
+
108
+ return {
109
+ id: data.id,
110
+ email: data.email,
111
+ name: data.name,
112
+ picture: data.picture,
113
+ };
114
+ }
115
+ }
116
+
117
+ export const oauthService = new OAuthService();
@@ -0,0 +1,82 @@
1
+ import { Role, Permission, RolePermissions, DEFAULT_PERMISSIONS } from './types';
2
+
3
+ export class RBACService {
4
+ private permissions: RolePermissions;
5
+
6
+ constructor(permissions: RolePermissions = DEFAULT_PERMISSIONS) {
7
+ this.permissions = permissions;
8
+ }
9
+
10
+ setPermissions(permissions: RolePermissions): void {
11
+ this.permissions = permissions;
12
+ }
13
+
14
+ addPermission(role: Role, permission: Permission): void {
15
+ if (!this.permissions[role]) {
16
+ this.permissions[role] = [];
17
+ }
18
+ if (!this.permissions[role].includes(permission)) {
19
+ this.permissions[role].push(permission);
20
+ }
21
+ }
22
+
23
+ removePermission(role: Role, permission: Permission): void {
24
+ if (this.permissions[role]) {
25
+ this.permissions[role] = this.permissions[role].filter(p => p !== permission);
26
+ }
27
+ }
28
+
29
+ getPermissions(role: Role): Permission[] {
30
+ return this.permissions[role] || [];
31
+ }
32
+
33
+ hasPermission(role: Role, requiredPermission: Permission): boolean {
34
+ const rolePermissions = this.getPermissions(role);
35
+
36
+ if (rolePermissions.includes('*')) {
37
+ return true;
38
+ }
39
+
40
+ if (rolePermissions.includes(requiredPermission)) {
41
+ return true;
42
+ }
43
+
44
+ const [requiredAction, requiredScope] = requiredPermission.split(':');
45
+
46
+ for (const perm of rolePermissions) {
47
+ const [action, scope] = perm.split(':');
48
+
49
+ if (action === '*' && (scope === '*' || scope === requiredScope)) {
50
+ return true;
51
+ }
52
+
53
+ if (action === requiredAction && (scope === '*' || scope === requiredScope)) {
54
+ return true;
55
+ }
56
+ }
57
+
58
+ return false;
59
+ }
60
+
61
+ hasRole(userRole: Role, requiredRole: Role): boolean {
62
+ const hierarchy: Role[] = ['guest', 'user', 'admin'];
63
+ const userIndex = hierarchy.indexOf(userRole as any);
64
+ const requiredIndex = hierarchy.indexOf(requiredRole as any);
65
+
66
+ if (userIndex === -1 || requiredIndex === -1) {
67
+ return userRole === requiredRole;
68
+ }
69
+
70
+ return userIndex >= requiredIndex;
71
+ }
72
+
73
+ authorize(permission: Permission) {
74
+ return (role: Role): boolean => this.hasPermission(role, permission);
75
+ }
76
+
77
+ authorizeRole(requiredRole: Role) {
78
+ return (role: Role): boolean => this.hasRole(role, requiredRole);
79
+ }
80
+ }
81
+
82
+ export const rbacService = new RBACService();
@@ -0,0 +1,69 @@
1
+ export type Role = 'admin' | 'user' | 'guest' | string;
2
+
3
+ export interface User {
4
+ id: string;
5
+ email: string;
6
+ password?: string;
7
+ role: Role;
8
+ name?: string;
9
+ picture?: string;
10
+ createdAt?: Date;
11
+ updatedAt?: Date;
12
+ }
13
+
14
+ export interface JWTPayload {
15
+ userId: string;
16
+ email: string;
17
+ role: Role;
18
+ }
19
+
20
+ export interface TokenPair {
21
+ accessToken: string;
22
+ refreshToken: string;
23
+ }
24
+
25
+ export interface AuthOptions {
26
+ jwtSecret?: string;
27
+ jwtExpiresIn?: string;
28
+ refreshSecret?: string;
29
+ refreshExpiresIn?: string;
30
+ googleClientId?: string;
31
+ googleClientSecret?: string;
32
+ googleRedirectUri?: string;
33
+ }
34
+
35
+ export interface AuthUserRequest extends Request {
36
+ user?: JWTPayload;
37
+ }
38
+
39
+ export type Permission = string;
40
+
41
+ export interface RolePermissions {
42
+ [role: string]: Permission[];
43
+ }
44
+
45
+ export const DEFAULT_PERMISSIONS: RolePermissions = {
46
+ admin: ['*'],
47
+ user: ['read', 'write:own'],
48
+ guest: ['read:public'],
49
+ };
50
+
51
+ export interface LoginCredentials {
52
+ email: string;
53
+ password: string;
54
+ }
55
+
56
+ export interface RegisterData extends LoginCredentials {
57
+ name?: string;
58
+ role?: Role;
59
+ }
60
+
61
+ export interface OAuthProvider {
62
+ name: string;
63
+ clientId: string;
64
+ clientSecret: string;
65
+ redirectUri: string;
66
+ authorizationUrl: string;
67
+ tokenUrl: string;
68
+ userInfoUrl: string;
69
+ }
@@ -0,0 +1,120 @@
1
+ import { z } from 'zod';
2
+
3
+ export const envSchema = z.object({
4
+ NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
5
+ PORT: z.string().default('3000'),
6
+ DATABASE_URL: z.string().optional(),
7
+ REDIS_URL: z.string().default('redis://localhost:6379'),
8
+ JWT_SECRET: z.string().min(32).optional(),
9
+ JWT_EXPIRES_IN: z.string().default('7d'),
10
+ JWT_REFRESH_SECRET: z.string().min(32).optional(),
11
+ JWT_REFRESH_EXPIRES_IN: z.string().default('30d'),
12
+ GOOGLE_CLIENT_ID: z.string().optional(),
13
+ GOOGLE_CLIENT_SECRET: z.string().optional(),
14
+ GOOGLE_REDIRECT_URI: z.string().optional(),
15
+ SMTP_HOST: z.string().optional(),
16
+ SMTP_PORT: z.string().default('587'),
17
+ SMTP_USER: z.string().optional(),
18
+ SMTP_PASS: z.string().optional(),
19
+ SMTP_FROM: z.string().optional(),
20
+ TWILIO_ACCOUNT_SID: z.string().optional(),
21
+ TWILIO_AUTH_TOKEN: z.string().optional(),
22
+ TWILIO_PHONE_NUMBER: z.string().optional(),
23
+ SLACK_WEBHOOK_URL: z.string().optional(),
24
+ RATE_LIMIT_WINDOW: z.string().default('1m'),
25
+ RATE_LIMIT_LIMIT: z.string().default('100'),
26
+ LOG_LEVEL: z.enum(['fatal', 'error', 'warn', 'info', 'debug', 'trace']).default('info'),
27
+ });
28
+
29
+ export type EnvConfig = z.infer<typeof envSchema>;
30
+
31
+ export interface ConfigOptions {
32
+ schema?: z.ZodSchema;
33
+ envPath?: string;
34
+ validate?: boolean;
35
+ }
36
+
37
+ class ConfigManager {
38
+ private config: EnvConfig | null = null;
39
+ private schema: z.ZodSchema;
40
+ private validate: boolean;
41
+
42
+ constructor(options: ConfigOptions = {}) {
43
+ this.schema = options.schema || envSchema;
44
+ this.validate = options.validate ?? true;
45
+ }
46
+
47
+ load(): EnvConfig {
48
+ if (this.config) return this.config;
49
+
50
+ const env: Record<string, string | undefined> = {};
51
+
52
+ for (const key of Object.keys(this.schema.shape)) {
53
+ env[key] = process.env[key];
54
+ }
55
+
56
+ if (this.validate) {
57
+ const result = this.schema.safeParse(env);
58
+ if (!result.success) {
59
+ const errors = result.error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', ');
60
+ throw new Error(`Config validation failed: ${errors}`);
61
+ }
62
+ this.config = result.data;
63
+ } else {
64
+ this.config = env as EnvConfig;
65
+ }
66
+
67
+ return this.config;
68
+ }
69
+
70
+ get<K extends keyof EnvConfig>(key: K): EnvConfig[K] {
71
+ if (!this.config) this.load();
72
+ return this.config![key];
73
+ }
74
+
75
+ int(key: keyof EnvConfig): number {
76
+ const value = this.get(key);
77
+ if (typeof value === 'string') return parseInt(value, 10);
78
+ return Number(value);
79
+ }
80
+
81
+ bool(key: keyof EnvConfig): boolean {
82
+ const value = this.get(key);
83
+ if (typeof value === 'boolean') return value;
84
+ if (typeof value === 'string') return value.toLowerCase() === 'true';
85
+ return Boolean(value);
86
+ }
87
+
88
+ isProduction(): boolean {
89
+ return this.get('NODE_ENV') === 'production';
90
+ }
91
+
92
+ isDevelopment(): boolean {
93
+ return this.get('NODE_ENV') === 'development';
94
+ }
95
+
96
+ isTest(): boolean {
97
+ return this.get('NODE_ENV') === 'test';
98
+ }
99
+
100
+ getAll(): EnvConfig {
101
+ if (!this.config) this.load();
102
+ return this.config!;
103
+ }
104
+ }
105
+
106
+ const globalConfig = new ConfigManager();
107
+
108
+ export const config = {
109
+ load: () => globalConfig.load(),
110
+ get: <K extends keyof EnvConfig>(key: K) => globalConfig.get(key),
111
+ int: (key: keyof EnvConfig) => globalConfig.int(key),
112
+ bool: (key: keyof EnvConfig) => globalConfig.bool(key),
113
+ isProduction: () => globalConfig.isProduction(),
114
+ isDevelopment: () => globalConfig.isDevelopment(),
115
+ isTest: () => globalConfig.isTest(),
116
+ getAll: () => globalConfig.getAll(),
117
+ create: (options?: ConfigOptions) => new ConfigManager(options),
118
+ };
119
+
120
+ export default config;
package/src/index.ts ADDED
@@ -0,0 +1,16 @@
1
+ export { AuthService, createAuth, auth, Auth } from './auth';
2
+ export { QueueManager, createQueue, queue } from './queue';
3
+ export { notify, notification } from './notifications';
4
+ export { logger } from './logger';
5
+ export { rateLimit, createRateLimiter } from './rate-limit';
6
+ export { config } from './config';
7
+ export { ResponseHelper, response } from './response';
8
+ export { createApp, createExpressApp, SaaSAppBuilder, PluginManager, Plugin, AppOptions } from './plugin';
9
+
10
+ export { AuthOptions, User, JWTPayload, TokenPair, LoginCredentials, RegisterData, Role, Permission, RolePermissions } from './auth/types';
11
+ export { QueueOptions, JobData, JobProcessor } from './queue';
12
+ export { EmailOptions, SMSOptions, WebhookOptions, SlackOptions } from './notifications';
13
+ export { LoggerConfig, LogLevel } from './logger';
14
+ export { RateLimitOptions } from './rate-limit';
15
+ export { EnvConfig, ConfigOptions } from './config';
16
+ export { ApiResponse, PaginatedResponse, ErrorResponse } from './response';
@@ -0,0 +1,110 @@
1
+ import pino, { Logger, LoggerOptions, Bindings } from 'pino';
2
+ import { config } from '../config';
3
+
4
+ export type LogLevel = 'fatal' | 'error' | 'warn' | 'info' | 'debug' | 'trace';
5
+
6
+ export interface LoggerConfig extends Partial<LoggerOptions> {
7
+ level?: LogLevel;
8
+ name?: string;
9
+ prettyPrint?: boolean;
10
+ }
11
+
12
+ export interface RequestLoggerOptions {
13
+ logLevel?: LogLevel;
14
+ autoLogging?: boolean;
15
+ }
16
+
17
+ class LoggerManager {
18
+ private loggers: Map<string, Logger> = new Map();
19
+ private defaultLogger: Logger;
20
+
21
+ constructor() {
22
+ const level = (config.get('LOG_LEVEL') as LogLevel) || 'info';
23
+ this.defaultLogger = pino({
24
+ level,
25
+ name: 'saas-backend-kit',
26
+ formatters: {
27
+ bindings: (bindings: Bindings) => ({
28
+ ...bindings,
29
+ service: 'saas-backend-kit',
30
+ }),
31
+ },
32
+ });
33
+ }
34
+
35
+ createLogger(options: LoggerConfig = {}): Logger {
36
+ const name = options.name || 'default';
37
+
38
+ if (this.loggers.has(name)) {
39
+ return this.loggers.get(name)!;
40
+ }
41
+
42
+ const level = options.level || (config.get('LOG_LEVEL') as LogLevel) || 'info';
43
+
44
+ const logger = pino({
45
+ level,
46
+ name: options.name,
47
+ ...options,
48
+ });
49
+
50
+ this.loggers.set(name, logger);
51
+ return logger;
52
+ }
53
+
54
+ getLogger(name?: string): Logger {
55
+ if (name) {
56
+ return this.loggers.get(name) || this.defaultLogger;
57
+ }
58
+ return this.defaultLogger;
59
+ }
60
+
61
+ child(bindings: Bindings, options?: { name?: string }): Logger {
62
+ const name = options?.name || 'child';
63
+ const parent = options?.name ? this.getLogger(name) : this.defaultLogger;
64
+ return parent.child(bindings);
65
+ }
66
+ }
67
+
68
+ const loggerManager = new LoggerManager();
69
+
70
+ export const logger = {
71
+ info: (message: string, ...args: unknown[]) => loggerManager.getLogger().info(message, ...args),
72
+ warn: (message: string, ...args: unknown[]) => loggerManager.getLogger().warn(message, ...args),
73
+ error: (message: string, ...args: unknown[]) => loggerManager.getLogger().error(message, ...args),
74
+ debug: (message: string, ...args: unknown[]) => loggerManager.getLogger().debug(message, ...args),
75
+ trace: (message: string, ...args: unknown[]) => loggerManager.getLogger().trace(message, ...args),
76
+ fatal: (message: string, ...args: unknown[]) => loggerManager.getLogger().fatal(message, ...args),
77
+ child: (bindings: Bindings, options?: { name?: string }) => loggerManager.child(bindings, options),
78
+ create: (options?: LoggerConfig) => loggerManager.createLogger(options),
79
+ get: (name?: string) => loggerManager.getLogger(name),
80
+ };
81
+
82
+ export function createRequestLogger(options: RequestLoggerOptions = {}) {
83
+ const logLevel = options.logLevel || 'info';
84
+ const logger = loggerManager.getLogger('http');
85
+
86
+ return function requestLogger(
87
+ req: { method: string; url: string; headers: Record<string, string | string[] | undefined> },
88
+ res: { statusCode: number; statusMessage?: string },
89
+ elapsed: number
90
+ ) {
91
+ const log = logger.child({
92
+ method: req.method,
93
+ url: req.url,
94
+ status: res.statusCode,
95
+ responseTime: elapsed,
96
+ ip: req.headers['x-forwarded-for'] || req.headers['x-real-ip'] || 'unknown',
97
+ userAgent: req.headers['user-agent'],
98
+ });
99
+
100
+ if (res.statusCode >= 500) {
101
+ log.error(`Request completed`);
102
+ } else if (res.statusCode >= 400) {
103
+ log.warn(`Request completed`);
104
+ } else {
105
+ log.info(`Request completed`);
106
+ }
107
+ };
108
+ }
109
+
110
+ export default logger;