├── .eslintrc.js
├── .gitignore
├── .prettierrc
├── README.md
├── nest-cli.json
├── package-lock.json
├── package.json
├── rss-parser.d.ts
├── src
├── app.controller.spec.ts
├── app.controller.ts
├── app.module.ts
├── app.service.ts
├── badge
│ ├── badge.controller.ts
│ ├── badge.module.ts
│ ├── badge.service.ts
│ ├── svg.service.ts
│ └── velog-api.service.ts
├── constants
│ └── graphql-queries.ts
├── interfaces
│ ├── feed-item.interface.ts
│ └── velog-api.interface.ts
├── main.ts
└── utils
│ ├── rss-parser.ts
│ └── svg-generator.ts
├── test
├── app.e2e-spec.ts
└── jest-e2e.json
├── tsconfig.build.json
├── tsconfig.json
└── vercel.json
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parser: '@typescript-eslint/parser',
3 | parserOptions: {
4 | project: 'tsconfig.json',
5 | tsconfigRootDir: __dirname,
6 | sourceType: 'module',
7 | },
8 | plugins: ['@typescript-eslint/eslint-plugin'],
9 | extends: [
10 | 'plugin:@typescript-eslint/recommended',
11 | 'plugin:prettier/recommended',
12 | ],
13 | root: true,
14 | env: {
15 | node: true,
16 | jest: true,
17 | },
18 | ignorePatterns: ['.eslintrc.js'],
19 | rules: {
20 | '@typescript-eslint/interface-name-prefix': 'off',
21 | '@typescript-eslint/explicit-function-return-type': 'off',
22 | '@typescript-eslint/explicit-module-boundary-types': 'off',
23 | '@typescript-eslint/no-explicit-any': 'off',
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # compiled output
2 | /dist
3 | /node_modules
4 |
5 | # Logs
6 | logs
7 | *.log
8 | npm-debug.log*
9 | pnpm-debug.log*
10 | yarn-debug.log*
11 | yarn-error.log*
12 | lerna-debug.log*
13 |
14 | # OS
15 | .DS_Store
16 |
17 | # Tests
18 | /coverage
19 | /.nyc_output
20 |
21 | # IDEs and editors
22 | /.idea
23 | .project
24 | .classpath
25 | .c9/
26 | *.launch
27 | .settings/
28 | *.sublime-workspace
29 |
30 | # IDE - VSCode
31 | .vscode/*
32 | !.vscode/settings.json
33 | !.vscode/tasks.json
34 | !.vscode/launch.json
35 | !.vscode/extensions.json
36 | .vercel
37 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "all"
4 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Velog GitHub Badge 🏷️
4 |
5 |
6 | Velog 활동을 GitHub 프로필에 표시해 보자
7 |
8 |
9 |
15 |
16 |
17 | 주요 기능 •
18 | 사용법 •
19 | 커스터마이징
20 |
21 |
22 | | Light Mode | Dark Mode |
23 | |-------------|-----------|
24 | |  |  |
25 |
26 |
27 |
28 | ## 주요 기능
29 |
30 | - Velog 최신 포스트 표시
31 | - 인기 태그 노출
32 | - 총 좋아요 수 집계
33 | - 다크/라이트 테마 지원
34 | - 손쉬운 커스터마이징
35 | - 실시간 업데이트
36 |
37 |
38 |
39 |
40 | ## 사용법
41 | GitHub 프로필 README.md 파일에 다음 코드를 추가하세요
42 | ```
43 | 
44 | ```
45 | **YourUsername을 반드시 여러분의 Velog 사용자 이름으로 변경해주세요!**
46 |
47 |
48 |
49 | ## 커스터마이징
50 | 배지의 모양을 다음 옵션으로 설정할 수 있습니다
51 |
52 | - theme: 배지의 테마 설정 (light 또는 dark, 기본값: light)
53 | - posts: 표시할 최근 게시물의 수 설정 (기본값: 1, 최대: 3)
54 |
55 | ```
56 | 
57 | ```
58 |
59 |
60 |
61 | ---
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | 이 프로젝트가 마음에 드셨다면 ⭐️을 눌러주세요!
74 | 여러분의 ⭐️ 하나하나가 프로젝트의 발전에 큰 힘이 됩니다!
75 |
76 |
77 |
78 |
79 | Created with ❤️ by Antraxmin
80 |
81 |
--------------------------------------------------------------------------------
/nest-cli.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/nest-cli",
3 | "collection": "@nestjs/schematics",
4 | "sourceRoot": "src",
5 | "compilerOptions": {
6 | "deleteOutDir": true
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "velog-github-badge",
3 | "version": "0.0.1",
4 | "description": "",
5 | "author": "",
6 | "private": true,
7 | "license": "UNLICENSED",
8 | "scripts": {
9 | "build": "nest build",
10 | "vercel-build": "npm run build",
11 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
12 | "start": "nest start",
13 | "start:dev": "nest start --watch",
14 | "start:debug": "nest start --debug --watch",
15 | "start:prod": "node dist/main",
16 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
17 | "test": "jest",
18 | "test:watch": "jest --watch",
19 | "test:cov": "jest --coverage",
20 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
21 | "test:e2e": "jest --config ./test/jest-e2e.json"
22 | },
23 | "dependencies": {
24 | "@nestjs/common": "^10.0.0",
25 | "@nestjs/core": "^10.0.0",
26 | "@nestjs/platform-express": "^10.0.0",
27 | "axios": "^1.7.2",
28 | "reflect-metadata": "^0.1.13",
29 | "rss-parser": "^3.13.0",
30 | "rxjs": "^7.8.1"
31 | },
32 | "devDependencies": {
33 | "@nestjs/cli": "^10.0.0",
34 | "@nestjs/schematics": "^10.0.0",
35 | "@nestjs/testing": "^10.0.0",
36 | "@types/express": "^4.17.17",
37 | "@types/jest": "^29.5.12",
38 | "@types/node": "^20.3.1",
39 | "@types/supertest": "^2.0.12",
40 | "@typescript-eslint/eslint-plugin": "^6.0.0",
41 | "@typescript-eslint/parser": "^6.0.0",
42 | "eslint": "^8.42.0",
43 | "eslint-config-prettier": "^9.0.0",
44 | "eslint-plugin-prettier": "^5.0.0",
45 | "jest": "^29.7.0",
46 | "prettier": "^3.0.0",
47 | "source-map-support": "^0.5.21",
48 | "supertest": "^6.3.3",
49 | "ts-jest": "^29.2.3",
50 | "ts-loader": "^9.4.3",
51 | "ts-node": "^10.9.1",
52 | "tsconfig-paths": "^4.2.0",
53 | "typescript": "^5.1.3"
54 | },
55 | "jest": {
56 | "moduleFileExtensions": [
57 | "js",
58 | "json",
59 | "ts"
60 | ],
61 | "rootDir": "src",
62 | "testRegex": ".*\\.spec\\.ts$",
63 | "transform": {
64 | "^.+\\.(t|j)s$": "ts-jest"
65 | },
66 | "collectCoverageFrom": [
67 | "**/*.(t|j)s"
68 | ],
69 | "coverageDirectory": "../coverage",
70 | "testEnvironment": "node"
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/rss-parser.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'rss-parser' {
2 | class Parser {
3 | constructor(options?: any);
4 | parseURL(url: string): Promise;
5 | }
6 | export = Parser;
7 | }
--------------------------------------------------------------------------------
/src/app.controller.spec.ts:
--------------------------------------------------------------------------------
1 | import { Test, TestingModule } from '@nestjs/testing';
2 | import { AppController } from './app.controller';
3 | import { AppService } from './app.service';
4 |
5 | describe('AppController', () => {
6 | let appController: AppController;
7 |
8 | beforeEach(async () => {
9 | const app: TestingModule = await Test.createTestingModule({
10 | controllers: [AppController],
11 | providers: [AppService],
12 | }).compile();
13 |
14 | appController = app.get(AppController);
15 | });
16 |
17 | describe('root', () => {
18 | it('should return "Hello World!"', () => {
19 | expect(appController.getHello()).toBe('Hello World!');
20 | });
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/src/app.controller.ts:
--------------------------------------------------------------------------------
1 | import { Controller, Get } from '@nestjs/common';
2 | import { AppService } from './app.service';
3 |
4 | @Controller()
5 | export class AppController {
6 | constructor(private readonly appService: AppService) {}
7 |
8 | @Get()
9 | getHello(): string {
10 | return this.appService.getHello();
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/app.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { BadgeModule } from './badge/badge.module';
3 |
4 | @Module({
5 | imports: [BadgeModule],
6 | })
7 | export class AppModule {}
--------------------------------------------------------------------------------
/src/app.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 |
3 | @Injectable()
4 | export class AppService {
5 | getHello(): string {
6 | return 'Hello World!';
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/badge/badge.controller.ts:
--------------------------------------------------------------------------------
1 | import { Controller, Get, Param, Res, Query } from '@nestjs/common';
2 | import { Response } from 'express';
3 | import { BadgeService } from './badge.service';
4 |
5 | @Controller('badge')
6 | export class BadgeController {
7 | constructor(private readonly badgeService: BadgeService) {}
8 |
9 | @Get(':username')
10 | async getBadge(
11 | @Param('username') username: string,
12 | @Query('theme') theme: string = 'light',
13 | @Query('posts') posts: number = 5,
14 | @Res() res: Response,
15 | ) {
16 | const svg = await this.badgeService.generateBadge(username, theme, posts);
17 | res.setHeader('Content-Type', 'image/svg+xml');
18 | res.setHeader('Cache-Control', 's-maxage=3600, stale-while-revalidate');
19 | res.send(svg);
20 | }
21 | }
--------------------------------------------------------------------------------
/src/badge/badge.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { BadgeController } from './badge.controller';
3 | import { BadgeService } from './badge.service';
4 | import { RSSParserService } from '../utils/rss-parser';
5 | import { VelogAPIService } from './velog-api.service';
6 | import { SVGService } from './svg.service';
7 |
8 | @Module({
9 | controllers: [BadgeController],
10 | providers: [BadgeService,
11 | RSSParserService,
12 | VelogAPIService,
13 | SVGService],
14 | })
15 | export class BadgeModule {}
--------------------------------------------------------------------------------
/src/badge/badge.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable, Logger } from '@nestjs/common';
2 | import { SVGService } from './svg.service';
3 | import { VelogAPIService } from './velog-api.service';
4 | import { RSSParserService } from '../utils/rss-parser';
5 | import { Feed, FeedItem } from '../interfaces/feed-item.interface';
6 |
7 | @Injectable()
8 | export class BadgeService {
9 | private readonly logger = new Logger(BadgeService.name);
10 |
11 | constructor(
12 | private readonly rssParserService: RSSParserService,
13 | private readonly velogAPIService: VelogAPIService,
14 | private readonly svgService: SVGService,
15 | ) {}
16 |
17 | async generateBadge(username: string, theme: string, posts: number): Promise {
18 | try {
19 | const [feed, totalLikes, tags] = await Promise.all([
20 | this.getFeed(username),
21 | this.velogAPIService.getTotalLikes(username),
22 | this.velogAPIService.getPopularTags(username),
23 | ]);
24 |
25 | const recentPosts = this.getRecentPosts(feed.items, posts);
26 | console.log(recentPosts)
27 |
28 | return this.svgService.generateSVG(username, recentPosts, theme, totalLikes, tags);
29 | } catch (error) {
30 | this.logger.error(`Error generating badge for ${username}: ${error.message}`);
31 | throw error;
32 | }
33 | }
34 |
35 | private async getFeed(username: string): Promise {
36 | try {
37 | return await this.rssParserService.parseRSS(`https://v2.velog.io/rss/${username}`);
38 | } catch (error) {
39 | this.logger.error(`Error fetching RSS feed for ${username}: ${error.message}`);
40 | return { items: [] };
41 | }
42 | }
43 |
44 | private getRecentPosts(items: FeedItem[], count: number): FeedItem[] {
45 | return items.slice(0, count);
46 | }
47 | }
--------------------------------------------------------------------------------
/src/badge/svg.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 | import { generateSVG as generateSVGUtil } from '../utils/svg-generator';
3 | import { FeedItem } from '../interfaces/feed-item.interface';
4 |
5 | @Injectable()
6 | export class SVGService {
7 | generateSVG(username: string, items: FeedItem[], theme: string, totalLikes: number, tags: string[]): string {
8 | return generateSVGUtil(username, items, theme, totalLikes, tags);
9 | }
10 | }
--------------------------------------------------------------------------------
/src/badge/velog-api.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable, Logger } from '@nestjs/common';
2 | import axios from 'axios';
3 | import { GET_USER_POSTS, GET_POPULAR_TAGS } from '../constants/graphql-queries';
4 | import { Post, Tag } from '../interfaces/velog-api.interface';
5 |
6 | interface VelogPost {
7 | id: string;
8 | title: string;
9 | likes: number;
10 | }
11 |
12 | @Injectable()
13 | export class VelogAPIService {
14 | private readonly logger = new Logger(VelogAPIService.name);
15 | private readonly VELOG_API_ENDPOINT = 'https://v2.velog.io/graphql';
16 |
17 | async getPostsWithLikes(username: string): Promise {
18 | const query = `
19 | query {
20 | posts(username: "${username}") {
21 | id
22 | title
23 | likes
24 | }
25 | }
26 | `;
27 |
28 | try {
29 | const response = await axios.post(this.VELOG_API_ENDPOINT, { query });
30 | return response.data.data.posts;
31 | } catch (error) {
32 | this.logger.error(`Error fetching posts for ${username}: ${error.message}`);
33 | throw error;
34 | }
35 | }
36 |
37 | async getTotalLikes(username: string): Promise {
38 | try {
39 | const posts = await this.getPostsWithLikes(username);
40 | return posts.reduce((sum, post) => sum + post.likes, 0);
41 | } catch (error) {
42 | this.logger.error(`Error calculating total likes for ${username}: ${error.message}`);
43 | throw error;
44 | }
45 | }
46 |
47 | async getPopularTags(username: string): Promise {
48 | try {
49 | const tags = await this.fetchUserTags(username);
50 | return this.getTopTags(tags);
51 | } catch (error) {
52 | this.logger.error(`Error fetching popular tags for ${username}: ${error.message}`);
53 | return [];
54 | }
55 | }
56 |
57 | private async fetchAllPosts(username: string): Promise {
58 | let allPosts: Post[] = [];
59 | let hasNextPage = true;
60 | let cursor = null;
61 |
62 | while (hasNextPage) {
63 | const response = await this.queryGraphQL(GET_USER_POSTS, { username, cursor });
64 | console.log('Full GraphQL Response:', JSON.stringify(response.data, null, 2));
65 |
66 | const { posts, pageInfo } = response.data.data.userPosts;
67 | console.log('Extracted Posts:', JSON.stringify(posts, null, 2));
68 |
69 | allPosts = [...allPosts, ...posts];
70 | hasNextPage = pageInfo.hasNextPage;
71 | cursor = pageInfo.endCursor;
72 | }
73 |
74 | return allPosts;
75 | }
76 |
77 | private calculateTotalLikes(posts: Post[]): number {
78 | this.logger.debug(`Calculating total likes for ${posts.length} posts`);
79 |
80 | return posts.reduce((sum, post, index) => {
81 | if (typeof post.likes !== 'number') {
82 | this.logger.warn(`Post at index ${index} has invalid 'likes' value: ${post.likes}`);
83 | return sum;
84 | }
85 |
86 | console.log(`Post ${index + 1}: ID=${post.id}, Likes=${post.likes}`);
87 | return sum + post.likes;
88 | }, 0);
89 | }
90 |
91 | private async fetchUserTags(username: string): Promise {
92 | const response = await this.queryGraphQL(GET_POPULAR_TAGS, { username });
93 | return response.data.data.userTags.tags;
94 | }
95 |
96 | private getTopTags(tags: Tag[]): string[] {
97 | return tags
98 | .sort((a, b) => b.posts_count - a.posts_count)
99 | .slice(0, 3)
100 | .map(tag => tag.name);
101 | }
102 |
103 | private async queryGraphQL(query: string, variables: any): Promise {
104 | try {
105 | return await axios.post(this.VELOG_API_ENDPOINT, { query, variables });
106 | } catch (error) {
107 | this.logger.error(`GraphQL query failed: ${error.message}`);
108 | throw error;
109 | }
110 | }
111 | }
--------------------------------------------------------------------------------
/src/constants/graphql-queries.ts:
--------------------------------------------------------------------------------
1 | export const GET_USER_POSTS = `
2 | query GetUserPosts($username: String!, $cursor: ID) {
3 | userPosts(username: $username, cursor: $cursor) {
4 | posts {
5 | id
6 | likes
7 | }
8 | pageInfo {
9 | endCursor
10 | hasNextPage
11 | }
12 | }
13 | }
14 | `;
15 |
16 | export const GET_POPULAR_TAGS = `
17 | query GetPopularTags($username: String!) {
18 | userTags(username: $username) {
19 | tags {
20 | name
21 | posts_count
22 | }
23 | }
24 | }
25 | `;
--------------------------------------------------------------------------------
/src/interfaces/feed-item.interface.ts:
--------------------------------------------------------------------------------
1 | export interface Feed {
2 | items: FeedItem[];
3 | }
4 |
5 | export interface FeedItem {
6 | title: string;
7 | link: string;
8 | pubDate: string;
9 | likes: number;
10 | }
--------------------------------------------------------------------------------
/src/interfaces/velog-api.interface.ts:
--------------------------------------------------------------------------------
1 | export interface Post {
2 | id: string;
3 | likes: number;
4 | }
5 |
6 | export interface Tag {
7 | name: string;
8 | posts_count: number;
9 | }
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { NestFactory } from '@nestjs/core';
2 | import { AppModule } from './app.module';
3 |
4 | async function bootstrap() {
5 | const app = await NestFactory.create(AppModule);
6 | app.enableCors();
7 | await app.listen(3000);
8 | }
9 | bootstrap();
10 |
--------------------------------------------------------------------------------
/src/utils/rss-parser.ts:
--------------------------------------------------------------------------------
1 | import { Injectable, Logger } from '@nestjs/common';
2 | import Parser from 'rss-parser';
3 | import { Feed, FeedItem } from '../interfaces/feed-item.interface';
4 |
5 | @Injectable()
6 | export class RSSParserService {
7 | private readonly parser = new Parser();
8 | private readonly logger = new Logger(RSSParserService.name);
9 |
10 | async parseRSS(url: string): Promise {
11 | try {
12 | const feed = await this.parser.parseURL(url);
13 | return {
14 | items: feed.items.map((item: any): FeedItem => ({
15 | title: item.title || '',
16 | link: item.link || '',
17 | pubDate: item.pubDate || '',
18 | likes: item.likes || '',
19 | })),
20 | };
21 | } catch (error) {
22 | this.logger.error(`Failed to parse RSS feed from ${url}: ${error.message}`);
23 | throw error;
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/src/utils/svg-generator.ts:
--------------------------------------------------------------------------------
1 | import { FeedItem } from '../interfaces/feed-item.interface';
2 |
3 | const WIDTH = 480;
4 | const HEIGHT = 240;
5 | const VELOG_LOGO_INLINE = `
6 |
14 | `;
15 |
16 |
17 | function getThemeColors(darkMode: boolean) {
18 | return {
19 | backgroundColor: darkMode ? '#0F172A' : '#F8FAFC',
20 | textColor: darkMode ? '#E2E8F0' : '#334155',
21 | accentColor: '#1EC997',
22 | secondaryColor: darkMode ? '#64748B' : '#94A3B8',
23 | cardColor: darkMode ? '#1E293B' : '#FFFFFF',
24 | };
25 | }
26 |
27 | function createBackground(colors: ReturnType, darkMode: boolean) {
28 | return `
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | `;
37 | }
38 |
39 | function createHeader(username: string, userProfileUrl: string, totalLikes: number, colors: ReturnType) {
40 | function getHeartPosition(totalLikes: number, width: number) {
41 | const textWidth = totalLikes.toLocaleString().length * 10;
42 | return width - textWidth - 40;
43 | }
44 |
45 | const heartX = getHeartPosition(totalLikes, WIDTH);
46 |
47 | return `
48 |
49 |
50 | ${VELOG_LOGO_INLINE}
51 |
52 |
53 | ${username}
54 |
55 |
56 |
57 |
60 |
61 |
62 | ${totalLikes.toLocaleString()}
63 |
64 | `;
65 | }
66 |
67 | function createLatestPosts(items: FeedItem[], colors: ReturnType) {
68 | let content = `
69 |
70 | Latest Posts
71 |
72 | `;
73 |
74 | items.slice(0, 3).forEach((item, index) => {
75 | const yPos = 100 + index * 30;
76 | const title = item.title.length > 40 ? item.title.substring(0, 37) + '...' : item.title;
77 | const date = new Date(item.pubDate).toLocaleDateString('ko-KR', { year: 'numeric', month: 'short', day: 'numeric' });
78 | content += `
79 |
80 |
81 |
82 | ${title}
83 |
84 |
85 | ${date}
86 |
87 |
88 | `;
89 | });
90 |
91 | return content;
92 | }
93 |
94 | function createTags(tags: string[], colors: ReturnType) {
95 | let content = `Top Tags`;
96 |
97 | const tagWidth = 90;
98 | const tagSpacing = 10;
99 | tags.slice(0, 5).forEach((tag, index) => {
100 | const xPos = 20 + index * (tagWidth + tagSpacing);
101 | const tagUrl = `https://velog.io/search?q=${encodeURIComponent(tag)}`;
102 | content += `
103 |
104 |
105 |
106 | ${tag}
107 |
108 |
109 | `;
110 | });
111 |
112 | return content;
113 | }
114 |
115 | export function generateSVG(username: string, items: FeedItem[], theme: string, totalLikes: number, tags: string[]): string {
116 | const darkMode = theme === 'dark';
117 | const colors = getThemeColors(darkMode);
118 | const userProfileUrl = `https://velog.io/@${username}`;
119 |
120 | let svgContent = `
121 |
127 | `;
128 |
129 | return svgContent;
130 | }
--------------------------------------------------------------------------------
/test/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { Test, TestingModule } from '@nestjs/testing';
2 | import { INestApplication } from '@nestjs/common';
3 | import * as request from 'supertest';
4 | import { AppModule } from './../src/app.module';
5 |
6 | describe('AppController (e2e)', () => {
7 | let app: INestApplication;
8 |
9 | beforeEach(async () => {
10 | const moduleFixture: TestingModule = await Test.createTestingModule({
11 | imports: [AppModule],
12 | }).compile();
13 |
14 | app = moduleFixture.createNestApplication();
15 | await app.init();
16 | });
17 |
18 | it('/ (GET)', () => {
19 | return request(app.getHttpServer())
20 | .get('/')
21 | .expect(200)
22 | .expect('Hello World!');
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/test/jest-e2e.json:
--------------------------------------------------------------------------------
1 | {
2 | "moduleFileExtensions": ["js", "json", "ts"],
3 | "rootDir": ".",
4 | "testEnvironment": "node",
5 | "testRegex": ".e2e-spec.ts$",
6 | "transform": {
7 | "^.+\\.(t|j)s$": "ts-jest"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
4 | }
5 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "declaration": true,
5 | "removeComments": true,
6 | "emitDecoratorMetadata": true,
7 | "esModuleInterop": true,
8 | "experimentalDecorators": true,
9 | "allowSyntheticDefaultImports": true,
10 | "target": "ES2021",
11 | "sourceMap": true,
12 | "outDir": "./dist",
13 | "baseUrl": "./",
14 | "incremental": true,
15 | "skipLibCheck": true,
16 | "strictNullChecks": false,
17 | "noImplicitAny": false,
18 | "strictBindCallApply": false,
19 | "forceConsistentCasingInFileNames": false,
20 | "noFallthroughCasesInSwitch": false
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 2,
3 | "builds": [
4 | {
5 | "src": "src/main.ts",
6 | "use": "@vercel/node"
7 | }
8 | ],
9 | "routes": [
10 | {
11 | "src": "/(.*)",
12 | "dest": "src/main.ts",
13 | "methods": [
14 | "GET",
15 | "POST",
16 | "PUT",
17 | "DELETE"
18 | ]
19 | }
20 | ]
21 | }
--------------------------------------------------------------------------------