CtrlK
BlogDocsLog inGet started
Tessl Logo

giuseppe-trisciuoglio/developer-kit

Comprehensive developer toolkit providing reusable skills for Java/Spring Boot, TypeScript/NestJS/React/Next.js, Python, PHP, AWS CloudFormation, AI/RAG, DevOps, and more.

89

Quality

89%

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Risky

Do not use without reviewing

Overview
Quality
Evals
Security
Files

guide-skills-nestjs.mdplugins/developer-kit-typescript/docs/

NestJS Skills Guide

This guide covers the comprehensive NestJS framework patterns and best practices available in the Developer Kit for building scalable, enterprise-ready Node.js applications.

Overview

NestJS is a progressive Node.js framework for building efficient, reliable, and scalable server-side applications. It provides a structured approach to building applications using TypeScript/JavaScript with full support for dependency injection, modular architecture, and extensive ecosystem integration.

Available Skills

1. nestjs

Core NestJS Framework Patterns with Drizzle ORM Integration

  • Purpose: Complete NestJS development guide covering controllers, providers, modules, authentication, security, testing, and database integration
  • Key Topics:
    • Module architecture and dependency injection
    • REST API controllers and routing
    • Services and providers
    • Middleware, guards, and interceptors
    • Authentication and authorization strategies
    • Database integration with Drizzle ORM (primary) and Prisma
    • Testing patterns (unit, integration, e2e)
    • Microservices architecture
    • GraphQL implementation
    • OpenAPI/Swagger documentation
    • Performance optimization

Core Patterns

Module Structure

import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
import { DatabaseModule } from '../database/database.module';

@Module({
  imports: [DatabaseModule],
  controllers: [UsersController],
  providers: [UsersService],
  exports: [UsersService],
})
export class UsersModule {}

Controller Pattern

import { Controller, Get, Post, Body, Param, Query, Put, Delete } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { UsersService } from './users.service';

@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Get()
  findAll(@Query() query: any) {
    return this.usersService.findAll(query);
  }

  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.usersService.findOne(id);
  }

  @Post()
  create(@Body() createUserDto: CreateUserDto) {
    return this.usersService.create(createUserDto);
  }

  @Put(':id')
  update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
    return this.usersService.update(id, updateUserDto);
  }

  @Delete(':id')
  remove(@Param('id') id: string) {
    return this.usersService.remove(id);
  }
}

Service with Dependency Injection

import { Injectable, NotFoundException } from '@nestjs/common';
import { DrizzleService } from '../drizzle/drizzle.service';
import { users } from '../db/schema';
import { eq } from 'drizzle-orm';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';

@Injectable()
export class UsersService {
  constructor(private readonly drizzle: DrizzleService) {}

  async create(createUserDto: CreateUserDto) {
    const [user] = await this.drizzle.db
      .insert(users)
      .values(createUserDto)
      .returning();
    return user;
  }

  async findAll() {
    return await this.drizzle.db.select().from(users);
  }

  async findOne(id: number) {
    const [user] = await this.drizzle.db
      .select()
      .from(users)
      .where(eq(users.id, id));
    if (!user) {
      throw new NotFoundException(`User with ID ${id} not found`);
    }
    return user;
  }

  async update(id: number, updateUserDto: UpdateUserDto) {
    const [user] = await this.drizzle.db
      .update(users)
      .set(updateUserDto)
      .where(eq(users.id, id))
      .returning();
    if (!user) {
      throw new NotFoundException(`User with ID ${id} not found`);
    }
    return user;
  }

  async remove(id: number) {
    const [user] = await this.drizzle.db
      .delete(users)
      .where(eq(users.id, id))
      .returning();
    if (!user) {
      throw new NotFoundException(`User with ID ${id} not found`);
    }
  }
}

Database Integration

Drizzle ORM Setup

Installation

npm install drizzle-orm pg
npm install -D drizzle-kit tsx @types/pg

Configuration

// drizzle.config.ts
import 'dotenv/config';
import { defineConfig } from 'drizzle-kit';

export default defineConfig({
  out: './drizzle',
  schema: './src/db/schema.ts',
  dialect: 'postgresql',
  dbCredentials: {
    url: process.env.DATABASE_URL!,
  },
});

Schema Definition

// src/db/schema.ts
import { pgTable, serial, varchar, timestamp } from 'drizzle-orm/pg-core';

export const users = pgTable('users', {
  id: serial('id').primaryKey(),
  email: varchar('email', { length: 255 }).notNull().unique(),
  name: varchar('name', { length: 255 }).notNull(),
  createdAt: timestamp('created_at').defaultNow(),
  updatedAt: timestamp('updated_at').defaultNow(),
});

Database Module

// src/db/database.module.ts
import { Module } from '@nestjs/common';
import { DrizzleService } from './drizzle.service';

@Module({
  providers: [DrizzleService],
  exports: [DrizzleService],
})
export class DatabaseModule {}

Authentication & Security

JWT Authentication

JWT Strategy

// src/auth/jwt.strategy.ts
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: process.env.JWT_SECRET,
    });
  }

  async validate(payload: any) {
    return { userId: payload.sub, username: payload.username };
  }
}

Auth Guard

// src/auth/jwt-auth.guard.ts
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}

Role-Based Access Control

Roles Guard

// src/auth/roles.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ROLES_KEY } from './roles.decorator';

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const requiredRoles = this.reflector.getAllAndOverride<string[]>(ROLES_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);

    if (!requiredRoles) {
      return true;
    }

    const { user } = context.switchToHttp().getRequest();
    return requiredRoles.some((role) => user.roles?.includes(role));
  }
}

Roles Decorator

// src/auth/roles.decorator.ts
import { SetMetadata } from '@nestjs/common';

export const ROLES_KEY = 'roles';
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);

Testing Patterns

Unit Testing

// src/users/users.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { UsersService } from './users.service';
import { DrizzleService } from '../drizzle/drizzle.service';

describe('UsersService', () => {
  let service: UsersService;
  let drizzle: DrizzleService;

  const mockDrizzleService = {
    db: {
      insert: jest.fn().mockReturnValue({
        values: jest.fn().mockReturnValue({
          returning: jest.fn().mockResolvedValue([{ id: 1, email: 'test@example.com', name: 'Test' }]),
        }),
      }),
      select: jest.fn().mockReturnValue({
        from: jest.fn().mockReturnValue({
          where: jest.fn().mockResolvedValue([{ id: 1, email: 'test@example.com', name: 'Test' }]),
        }),
        from: jest.fn().mockResolvedValue([{ id: 1, email: 'test@example.com', name: 'Test' }]),
      }),
      update: jest.fn().mockReturnValue({
        set: jest.fn().mockReturnValue({
          where: jest.fn().mockReturnValue({
            returning: jest.fn().mockResolvedValue([{ id: 1, email: 'test@example.com', name: 'Test Updated' }]),
          }),
        }),
      }),
      delete: jest.fn().mockReturnValue({
        where: jest.fn().mockReturnValue({
          returning: jest.fn().mockResolvedValue([{ id: 1 }]),
        }),
      }),
    },
  };

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        UsersService,
        { provide: DrizzleService, useValue: mockDrizzleService },
      ],
    }).compile();

    service = module.get<UsersService>(UsersService);
    drizzle = module.get<DrizzleService>(DrizzleService);
  });

  it('should create a user', async () => {
    const createUserDto = { email: 'test@example.com', name: 'Test User' };
    const result = await service.create(createUserDto);
    expect(result).toEqual(expect.objectContaining({ email: 'test@example.com' }));
    expect(drizzle.db.insert).toHaveBeenCalled();
  });

  it('should find all users', async () => {
    const result = await service.findAll();
    expect(result).toEqual(expect.any(Array));
    expect(drizzle.db.select).toHaveBeenCalled();
  });

  it('should find a user by id', async () => {
    const result = await service.findOne(1);
    expect(result).toEqual(expect.objectContaining({ id: 1 }));
  });

  it('should throw NotFoundException when user not found', async () => {
    jest.spyOn(drizzle.db, 'select').mockReturnValueOnce({
      from: jest.fn().mockReturnValue({
        where: jest.fn().mockResolvedValue([]),
      }),
    } as any);
    await expect(service.findOne(999)).rejects.toThrow(NotFoundException);
  });
});

Integration Testing

// src/users/users.controller.e2e-spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { UsersModule } from './users.module';
import { DrizzleService } from '../drizzle/drizzle.service';

describe('UsersController (e2e)', () => {
  let app: INestApplication;
  let drizzle: DrizzleService;

  const mockDrizzleService = {
    db: {
      select: jest.fn().mockReturnValue({
        from: jest.fn().mockResolvedValue([]),
      }),
    },
  };

  beforeEach(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [UsersModule],
    })
      .overrideProvider(DrizzleService)
      .useValue(mockDrizzleService)
      .compile();

    app = moduleFixture.createNestApplication();
    await app.init();
    drizzle = moduleFixture.get<DrizzleService>(DrizzleService);
  });

  it('/users (GET)', () => {
    return request(app.getHttpServer())
      .get('/users')
      .expect(200)
      .expect([]);
  });

  it('/users (POST)', () => {
    const createUserDto = {
      email: 'test@example.com',
      name: 'Test User',
    };

    return request(app.getHttpServer())
      .post('/users')
      .send(createUserDto)
      .expect(201);
  });

  afterEach(async () => {
    await app.close();
  });
});

Middleware & Interceptors

Logging Middleware

// src/common/middleware/logging.middleware.ts
import { Injectable, NestMiddleware, Logger } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class LoggingMiddleware implements NestMiddleware {
  private logger = new Logger('HTTP');

  use(req: Request, res: Response, next: NextFunction) {
    const { ip, method, originalUrl } = req;
    const userAgent = req.get('User-Agent') || '';
    const startTime = Date.now();

    res.on('finish', () => {
      const { statusCode } = res;
      const contentLength = res.get('Content-Length');
      const duration = Date.now() - startTime;

      this.logger.log(
        `${method} ${originalUrl} ${statusCode} ${contentLength} - ${userAgent} ${ip} - ${duration}ms`,
      );
    });

    next();
  }
}

Response Transform Interceptor

// src/common/interceptors/transform.interceptor.ts
import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

export interface Response<T> {
  data: T;
  success: boolean;
  timestamp: string;
}

@Injectable()
export class TransformInterceptor<T>
  implements NestInterceptor<T, Response<T>> {
  intercept(
    context: ExecutionContext,
    next: CallHandler,
  ): Observable<Response<T>> {
    return next.handle().pipe(
      map((data) => ({
        data,
        success: true,
        timestamp: new Date().toISOString(),
      })),
    );
  }
}

Validation & DTOs

DTO with Validation

// src/users/dto/create-user.dto.ts
import { IsEmail, IsString, MinLength } from 'class-validator';

export class CreateUserDto {
  @IsEmail()
  email: string;

  @IsString()
  @MinLength(2)
  name: string;

  @IsString()
  @MinLength(8)
  password: string;
}

Global Validation Pipe

// src/main.ts
import { ValidationPipe } from '@nestjs/common';

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

  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true,
      transform: true,
      transformOptions: {
        enableImplicitConversion: true,
      },
    }),
  );

  await app.listen(3000);
}

Configuration Management

Environment Configuration

// src/config/configuration.ts
export default () => ({
  port: parseInt(process.env.PORT, 10) || 3000,
  database: {
    host: process.env.DATABASE_HOST,
    port: parseInt(process.env.DATABASE_PORT, 10) || 5432,
    username: process.env.DATABASE_USERNAME,
    password: process.env.DATABASE_PASSWORD,
    database: process.env.DATABASE_NAME,
  },
  jwt: {
    secret: process.env.JWT_SECRET,
    expiresIn: process.env.JWT_EXPIRES_IN || '1h',
  },
});

Configuration Module Setup

// src/config/config.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import configuration from './configuration';

@Module({
  imports: [
    ConfigModule.forRoot({
      load: [configuration],
      isGlobal: true,
    }),
  ],
  providers: [ConfigService],
  exports: [ConfigService],
})
export class AppConfigModule {}

Performance Optimization

Caching with Redis

// src/cache/cache.module.ts
import { Module } from '@nestjs/common';
import { CacheModule } from '@nestjs/cache-manager';
import { redisStore } from 'cache-manager-redis-store';

@Module({
  imports: [
    CacheModule.register({
      store: redisStore,
      host: 'localhost',
      port: 6379,
    }),
  ],
  exports: [CacheModule],
})
export class AppCacheModule {}

Rate Limiting

// src/common/guards/throttle.guard.ts
import { Injectable } from '@nestjs/common';
import { ThrottlerGuard } from '@nestjs/throttler';

@Injectable()
export class AppThrottlerGuard extends ThrottlerGuard {
  protected errorMessage = 'Too many requests, please try again later.';
}

Error Handling

Global Exception Filter

// src/common/filters/http-exception.filter.ts
import {
  ExceptionFilter,
  Catch,
  ArgumentsHost,
  HttpException,
  HttpStatus,
  Logger,
} from '@nestjs/common';
import { Request, Response } from 'express';

@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  private logger = new Logger(AllExceptionsFilter.name);

  catch(exception: unknown, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const request = ctx.getRequest<Request>();

    let status: number;
    let message: string;

    if (exception instanceof HttpException) {
      status = exception.getStatus();
      const exceptionResponse = exception.getResponse();
      message = typeof exceptionResponse === 'string'
        ? exceptionResponse
        : (exceptionResponse as any).message;
    } else {
      status = HttpStatus.INTERNAL_SERVER_ERROR;
      message = 'Internal server error';
    }

    const errorResponse = {
      success: false,
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: request.url,
      method: request.method,
      message: message,
    };

    this.logger.error(
      `${request.method} ${request.url}`,
      JSON.stringify(errorResponse),
    );

    response.status(status).json(errorResponse);
  }
}

Microservices

Microservice Configuration

// src/main.ts
import { NestFactory } from '@nestjs/core';
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.createMicroservice<MicroserviceOptions>(
    AppModule,
    {
      transport: Transport.TCP,
      options: {
        host: '127.0.0.1',
        port: 8877,
      },
    },
  );

  await app.listen();
}

Message Pattern Handler

// src/math/math.controller.ts
import { Controller } from '@nestjs/common';
import { MessagePattern, Payload } from '@nestjs/microservices';

@Controller()
export class MathController {
  @MessagePattern({ cmd: 'sum' })
  accumulate(@Payload() data: number[]): number {
    return (data || []).reduce((a, b) => a + b, 0);
  }
}

GraphQL Integration

GraphQL Module Setup

// src/graphql/graphql.module.ts
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
import { join } from 'path';

@Module({
  imports: [
    GraphQLModule.forRoot<ApolloDriverConfig>({
      driver: ApolloDriver,
      autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
      sortSchema: true,
      playground: true,
      introspection: true,
    }),
  ],
})
export class GraphQLConfigModule {}

Resolver Example

// src/users/users.resolver.ts
import { Resolver, Query, Mutation, Args, Int } from '@nestjs/graphql';
import { UsersService } from './users.service';
import { User } from './entities/user.entity';
import { CreateUserInput } from './dto/create-user.input';

@Resolver(() => User)
export class UsersResolver {
  constructor(private readonly usersService: UsersService) {}

  @Query(() => [User], { name: 'users' })
  findAll() {
    return this.usersService.findAll();
  }

  @Query(() => User, { name: 'user' })
  findOne(@Args('id', { type: () => Int }) id: number) {
    return this.usersService.findOne(id);
  }

  @Mutation(() => User)
  createUser(@Args('createUserInput') createUserInput: CreateUserInput) {
    return this.usersService.create(createUserInput);
  }
}

OpenAPI/Swagger Documentation

Swagger Configuration

// src/main.ts
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';

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

  const config = new DocumentBuilder()
    .setTitle('NestJS API')
    .setDescription('The NestJS API description')
    .setVersion('1.0')
    .addBearerAuth()
    .build();

  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('api', app, document);

  await app.listen(3000);
}

API Documentation Decorators

// src/users/users.controller.ts
import {
  ApiTags,
  ApiOperation,
  ApiResponse,
  ApiParam,
  ApiBody
} from '@nestjs/swagger';

@ApiTags('users')
@Controller('users')
export class UsersController {

  @ApiOperation({ summary: 'Get all users' })
  @ApiResponse({ status: 200, description: 'Return all users', type: [User] })
  @Get()
  findAll() {
    return this.usersService.findAll();
  }

  @ApiOperation({ summary: 'Get user by ID' })
  @ApiParam({ name: 'id', description: 'User ID' })
  @ApiResponse({ status: 200, description: 'Return user', type: User })
  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.usersService.findOne(id);
  }
}

Best Practices

1. Project Structure

src/
├── common/           # Shared utilities, filters, guards, interceptors
├── config/           # Configuration files
├── database/         # Database related files
├── auth/             # Authentication and authorization
├── users/            # User module (example)
│   ├── dto/         # Data transfer objects
│   ├── entities/    # Database entities
│   ├── interfaces/  # TypeScript interfaces
│   ├── tests/       # Test files
│   ├── users.controller.ts
│   ├── users.module.ts
│   └── users.service.ts
├── app.module.ts
└── main.ts

2. Naming Conventions

  • Use descriptive names for modules, services, and controllers
  • Follow NestJS naming patterns (e.g., *.module.ts, *.service.ts, *.controller.ts)
  • Use PascalCase for classes and camelCase for variables and methods

3. Code Organization

  • Group related functionality in modules
  • Keep modules small and focused on a single responsibility
  • Use shared modules for common functionality

4. Dependency Injection

  • Prefer constructor injection
  • Use interfaces for better testability
  • Leverage NestJS's DI container for managing dependencies

5. Error Handling

  • Use built-in HTTP exceptions
  • Create custom exceptions for specific use cases
  • Implement global exception filters for consistent error responses

6. Validation

  • Use class-validator for DTO validation
  • Implement validation pipes globally
  • Create custom validators for complex validation logic

7. Security

  • Always validate and sanitize user input
  • Implement rate limiting for API endpoints
  • Use HTTPS in production
  • Keep dependencies updated

8. Testing

  • Write unit tests for services and controllers
  • Use integration tests for API endpoints
  • Mock external dependencies in tests
  • Maintain high test coverage

9. Performance

  • Implement caching strategies
  • Use database indexes appropriately
  • Optimize database queries
  • Monitor and profile application performance

10. Documentation

  • Document APIs with Swagger/OpenAPI
  • Use clear and descriptive comments
  • Maintain up-to-date README files
  • Include examples in documentation

Common Patterns

1. CRUD Operations

Create reusable CRUD patterns using base classes or services to reduce code duplication.

2. Pagination

Implement consistent pagination across all list endpoints using query parameters.

3. Soft Delete

Add soft delete functionality to entities for data recovery capabilities.

4. Audit Trail

Track create, update, and delete operations for compliance and debugging.

5. Event Sourcing

Use events to capture state changes for auditability and replay capabilities.

Integration with Other Technologies

1. WebSockets

import {
  WebSocketGateway,
  SubscribeMessage,
  MessageBody,
  WebSocketServer,
} from '@nestjs/websockets';
import { Server } from 'socket.io';

@WebSocketGateway()
export class EventsGateway {
  @WebSocketServer()
  server: Server;

  @SubscribeMessage('message')
  handleMessage(@MessageBody() data: string): void {
    this.server.emit('message', data);
  }
}

2. Task Scheduling

import { Injectable } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';

@Injectable()
export class TasksService {
  @Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT)
  handleDailyTask() {
    // Daily task logic
  }
}

3. File Upload

import {
  Controller,
  Post,
  UseInterceptors,
  UploadedFile,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';

@Controller('files')
export class FilesController {
  @Post('upload')
  @UseInterceptors(FileInterceptor('file'))
  uploadFile(@UploadedFile() file: Express.Multer.File) {
    // File upload logic
  }
}

Deployment Considerations

1. Environment Variables

  • Use environment variables for configuration
  • Never commit secrets to version control
  • Use .env files for development

2. Health Checks

Implement health check endpoints for monitoring:

import { Controller, Get } from '@nestjs/common';
import { HealthCheckService, HealthCheck, HealthCheckResult } from '@nestjs/terminus';

@Controller('health')
export class HealthController {
  constructor(private health: HealthCheckService) {}

  @Get()
  @HealthCheck()
  check(): Promise<HealthCheckResult> {
    return this.health.check([]);
  }
}

3. Logging

Implement structured logging for production:

import { LoggerService } from '@nestjs/common';

export class AppLogger implements LoggerService {
  log(message: any, context?: string) {
    // Structured logging implementation
  }
}

4. Containerization

Create Dockerfile for containerization:

FROM node:18-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY . .

RUN npm run build

EXPOSE 3000

CMD ["node", "dist/main"]

Conclusion

This guide provides a comprehensive overview of NestJS development patterns available in the Developer Kit. By following these patterns and best practices, you can build scalable, maintainable, and secure enterprise applications.

For specific implementation details and advanced patterns, refer to the NestJS skill and the official NestJS documentation.

plugins

developer-kit-typescript

README.md

CHANGELOG.md

context7.json

CONTRIBUTING.md

README_CN.md

README_ES.md

README_IT.md

README.md

tessl.json

tile.json