Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(backend): migrate to nestjs #68

Merged
merged 10 commits into from
Jun 3, 2024
Prev Previous commit
Next Next commit
chore(backend): configure sentry for observability
  • Loading branch information
tericcabrel committed May 23, 2024
commit 2f7d43a377880718bda952fa645d0a3f84c930cd
2 changes: 1 addition & 1 deletion apps/backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ WEB_AUTH_ERROR_URL=https://localhost:7500/auth/error
SESSION_LIFETIME=90# 90 days
SENTRY_DSN=
SENTRY_ENABLED=false
SNIPPET_RENDERER_URL=https://localhost:3000/dev
SNIPPET_RENDERER_API_URL=https://localhost:3000/dev
46 changes: 24 additions & 22 deletions apps/backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,34 +37,36 @@ yarn install
````
Create configuration file from the template
```shell
cp .env.template .env
cp .env.template .env.local

# Edit configuration to match your local environment and save
nano .env
nano .env.local
```

**Environment variables list**

| Variable | Description |
|----------------------|--------------------------------------------------------------------------|
| NODE_ENV | Node.js environment (default: development) |
| HOST | Host name where the application is running (default: https://localhost) |
| PORT | Port number of the application (default: 7501) |
| ENABLE_INTROSPECTION | Enable/Disable GraphQL introspection (must `false` in production) |
| DATABASE_URL | URL of the database |
| ADMIN_PASSWORD | Password of the default admin user. |
| CONVERTKIT_API_KEY | ConvertKit API key |
| CONVERTKIT_FORM_ID | ConvertKit Form ID |
| CONVERTKIT_TAG_ID | ConvertKit Tag ID |
| REQUEST_TIMEOUT | Max duration of a request (default: 30 seconds) |
| GITHUB_CLIENT_ID | GitHub application client ID for authentication with GitHub |
| GITHUB_CLIENT_SECRET | GitHub application client secret for authentication with GitHub |
| WEB_APP_URL | URL of the frontend the application that communicates with this app |
| WEB_AUTH_SUCCESS_URL | Callback URL of the frontend application when the authentication succeed |
| WEB_AUTH_ERROR_URL | Callback URL of the frontend application when the authentication failed |
| SESSION_LIFETIME | The session's lifetime when a user authenticate (default: 90 days) |
| SENTRY_DSN | Sentry DSN |
| SENTRY_ENABLED | Enable/Disable Sentry |
| Variable | Description |
|--------------------------|--------------------------------------------------------------------------------------|
| NODE_ENV | Node.js environment (default: development) |
| APP_VERSION | The current version of the application |
| HOST | Host name where the application is running (default: https://localhost) |
| PORT | Port number of the application (default: 7501) |
| ENABLE_INTROSPECTION | Enable/Disable GraphQL introspection (must `false` in production) |
| DATABASE_URL | URL of the database |
| ADMIN_PASSWORD | Password of the default admin user. |
| CONVERTKIT_API_KEY | ConvertKit API key |
| CONVERTKIT_FORM_ID | ConvertKit Form ID |
| CONVERTKIT_TAG_ID | ConvertKit Tag ID |
| REQUEST_TIMEOUT | Max duration of a request (default: 30 seconds) |
| GITHUB_CLIENT_ID | GitHub application client ID for authentication with GitHub |
| GITHUB_CLIENT_SECRET | GitHub application client secret for authentication with GitHub |
| WEB_APP_URL | URL of the frontend the application that communicates with this app |
| WEB_AUTH_SUCCESS_URL | Callback URL of the frontend application when the authentication succeed |
| WEB_AUTH_ERROR_URL | Callback URL of the frontend application when the authentication failed |
| SESSION_LIFETIME | The session's lifetime when a user authenticate (default: 90 days) |
| SENTRY_DSN | Sentry DSN |
| SENTRY_ENABLED | Enable/Disable Sentry |
| SNIPPET_RENDERER_API_URL | Base URL of the API (the current one) for generating the html content from a snippet |

Start the application
```bash
Expand Down
2 changes: 2 additions & 0 deletions apps/backend/env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ export type EnvironmentVariables = {
GITHUB_CLIENT_ID: string;
GITHUB_CLIENT_SECRET: string;
HOST: string;
NODE_ENV: string;
PORT: string;
REQUEST_TIMEOUT: string;
SENTRY_DSN: string;
SENTRY_ENABLED: string;
SESSION_LIFETIME: string;
SNIPPET_RENDERER_URL: string;
WEB_APP_URL: string;
Expand Down
1 change: 1 addition & 0 deletions apps/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"@nestjs/core": "10.3.8",
"@nestjs/platform-express": "10.3.8",
"@prisma/client": "5.14.0",
"@sentry/node": "8.3.0",
"reflect-metadata": "0.2.2",
"rxjs": "7.8.1",
"zod": "3.23.8"
Expand Down
2 changes: 1 addition & 1 deletion apps/backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import appConfig, { EnvironmentVariables, validate } from './configs/environment
controllers: [AppController],
imports: [
ConfigModule.forRoot({
envFilePath: ['.env', '.env.test'],
envFilePath: ['.env.local', '.env.test'],
isGlobal: true,
load: [appConfig],
validate,
Expand Down
4 changes: 4 additions & 0 deletions apps/backend/src/app.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ export class AppService {

const roles = await this.roleService.findAll();

if (Math.random() > 0.5) {
throw new Error('[Data Loader]: Role administrator not found');
}

this.logger.log(roles);

return 'Hello World!';
Expand Down
8 changes: 8 additions & 0 deletions apps/backend/src/configs/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,21 @@ export default registerAs('app', () => ({
env: process.env.NODE_ENV,
host: process.env.HOST,
port: parseInt(process.env.PORT ?? '7501', 10),
sentry: {
dsn: process.env.SENTRY_DSN,
enabled: process.env.SENTRY_ENABLED,
},
version: process.env.APP_VERSION,
}));

const EnvironmentVariablesSchema = z.object({
APP_VERSION: z.string(),
DATABASE_URL: z.string(),
HOST: z.string(),
NODE_ENV: z.union([z.literal('development'), z.literal('production'), z.literal('test')]),
PORT: z.number({ coerce: true }).min(7000).max(8000),
SENTRY_DSN: z.string(),
SENTRY_ENABLED: z.boolean({ coerce: true }),
});

export type EnvironmentVariables = z.infer<typeof EnvironmentVariablesSchema>;
Expand Down
22 changes: 22 additions & 0 deletions apps/backend/src/configs/instrument.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import * as Sentry from '@sentry/node';

const isSentryEnabled = () => {
if (process.env.SENTRY_ENABLED !== 'true') {
return false;
}

return process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'development';
};

Sentry.init({
dsn: process.env.SENTRY_DSN,
enabled: isSentryEnabled(),
environment: process.env.NODE_ENV,
integrations: [Sentry.prismaIntegration()],
// https://docs.sentry.io/platforms/javascript/guides/nestjs/configuration/sampling/#setting-a-uniform-sample-rate
tracesSampleRate: 0.6,
});

Sentry.setContext('app', {
version: process.env.APP_VERSION,
});
9 changes: 8 additions & 1 deletion apps/backend/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import './configs/instrument';

import { Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { NestFactory } from '@nestjs/core';
import { BaseExceptionFilter, HttpAdapterHost, NestFactory } from '@nestjs/core';
import * as Sentry from '@sentry/node';

import { AppModule } from './app.module';
import { EnvironmentVariables } from './types/common';

const bootstrap = async () => {
const app = await NestFactory.create(AppModule);

const { httpAdapter } = app.get(HttpAdapterHost);

Sentry.setupNestErrorHandler(app, new BaseExceptionFilter(httpAdapter));

const configService = app.get(ConfigService<EnvironmentVariables, true>);
const logger = new Logger('NestApplication');

Expand Down
3 changes: 2 additions & 1 deletion apps/backend/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"composite": true,
"strictNullChecks": true
},
"files": ["env.d.ts"],
"include": [
"./src/**/*"
],
Expand All @@ -20,7 +21,7 @@
"path": "../../packages/utils"
},
{
"path": "../../packages/logger-old"
"path": "../../packages/domain"
}
]
}
2 changes: 1 addition & 1 deletion packages/domain/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export { DomainModule } from './src/domain.module';
export { PrismaService } from './src/prisma.service';
export { PrismaService } from './src/services/prisma.service';
export { UserService } from './src/services/users/user.service';
export { RoleService } from './src/services/roles/role.service';
export { FolderService } from './src/services/folders/folder.service';
Expand Down
2 changes: 1 addition & 1 deletion packages/domain/src/domain.module.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { DynamicModule, Module, Provider } from '@nestjs/common';

import { DOMAIN_SERVICES_OPTIONS } from './constants';
import { PrismaService } from './prisma.service';
import { FolderService } from './services/folders/folder.service';
import { NewsletterService } from './services/newsletters/newsletter.service';
import { PrismaService } from './services/prisma.service';
import { RoleService } from './services/roles/role.service';
import { SessionService } from './services/sessions/session.service';
import { SnippetService } from './services/snippets/snippet.service';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { CreateFolderInput } from './inputs/create-folder-input';
import { CreateUserRootFolderInput } from './inputs/create-user-root-folder-input';
import { TestHelper } from '../../../tests/helpers';
import { DomainModule } from '../../domain.module';
import { PrismaService } from '../../prisma.service';
import { PrismaService } from '../prisma.service';
import { RoleService } from '../roles/role.service';

describe('Test Folder service', () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/domain/src/services/folders/folder.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { CreateFolderInput } from './inputs/create-folder-input';
import { CreateUserRootFolderInput } from './inputs/create-user-root-folder-input';
import { UpdateFolderInput } from './inputs/update-folder-input';
import { isFoldersContainRoot } from './utils/folders';
import { PrismaService } from '../../prisma.service';
import { PrismaService } from '../prisma.service';

@Injectable()
export class FolderService {
Expand Down
2 changes: 1 addition & 1 deletion packages/domain/src/services/roles/role.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { SnipcodeError, errors } from '@snipcode/utils';

import { CreateRoleInput } from './inputs/create-role-input';
import { Role, RoleName } from './role.entity';
import { PrismaService } from '../../prisma.service';
import { PrismaService } from '../prisma.service';

@Injectable()
export class RoleService {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Session } from './session.entity';
import { SessionService } from './session.service';
import { TestHelper } from '../../../tests/helpers';
import { DomainModule } from '../../domain.module';
import { PrismaService } from '../../prisma.service';
import { PrismaService } from '../prisma.service';

describe('Test Session Service', function () {
let sessionService: SessionService;
Expand Down
2 changes: 1 addition & 1 deletion packages/domain/src/services/sessions/session.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';

import { CreateSessionInput } from './inputs/create-session-input';
import { Session } from './session.entity';
import { PrismaService } from '../../prisma.service';
import { PrismaService } from '../prisma.service';

@Injectable()
export class SessionService {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Snippet } from './snippet.entity';
import { SnippetService } from './snippet.service';
import { TestHelper } from '../../../tests/helpers';
import { DomainModule } from '../../domain.module';
import { PrismaService } from '../../prisma.service';
import { PrismaService } from '../prisma.service';
import { RoleService } from '../roles/role.service';

describe('Test Snippet service', () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/domain/src/services/snippets/snippet.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { CreateSnippetInput } from './inputs/create-snippet-input';
import { DeleteSnippetInput } from './inputs/delete-snippet-input';
import { UpdateSnippetInput } from './inputs/update-snippet-input';
import { Snippet, SnippetVisibility } from './snippet.entity';
import { PrismaService } from '../../prisma.service';
import { PrismaService } from '../prisma.service';

const MAX_ITEM_PER_PAGE = 50;

Expand Down
2 changes: 1 addition & 1 deletion packages/domain/src/services/users/user.service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { User } from './user.entity';
import { UserService } from './user.service';
import { TestHelper } from '../../../tests/helpers';
import { DomainModule } from '../../domain.module';
import { PrismaService } from '../../prisma.service';
import { PrismaService } from '../prisma.service';
import { RoleService } from '../roles/role.service';

describe('Test User service', () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/domain/src/services/users/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import { generateFromEmail } from 'unique-username-generator';
import { CreateUserInput } from './inputs/create-user-input';
import { UpdateUserInput } from './inputs/update-user-input';
import { User } from './user.entity';
import { PrismaService } from '../../prisma.service';
import { CreateUserRootFolderInput } from '../folders/inputs/create-user-root-folder-input';
import { PrismaService } from '../prisma.service';
import { Role } from '../roles/role.entity';

@Injectable()
Expand Down
2 changes: 1 addition & 1 deletion packages/domain/tests/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { randEmail, randFullName, randImg, randNumber, randTimeZone, randUserName, randWord } from '@ngneat/falso';

import { PrismaService } from '../src/prisma.service';
import { Folder } from '../src/services/folders/folder.entity';
import { CreateFolderInput } from '../src/services/folders/inputs/create-folder-input';
import { CreateUserRootFolderInput } from '../src/services/folders/inputs/create-user-root-folder-input';
import { UpdateFolderInput } from '../src/services/folders/inputs/update-folder-input';
import { PrismaService } from '../src/services/prisma.service';
import { Role, RoleName } from '../src/services/roles/role.entity';
import { CreateSessionInput } from '../src/services/sessions/inputs/create-session-input';
import { Session } from '../src/services/sessions/session.entity';
Expand Down
Loading