Setting up the server

Setting up the Server

We're not super far along on the front end, but I want to get it talking to a server and database so we can make sure the vertical slice is running nice.

I decided to set the server up with NestJS (not to be confused with Next.js) instead of Express since this is a learning project and I've used Express in the past. It also seems like NestJS is set up with TypeScript, is opinionated, and uses some patterns I'm less familiar with. I like those things!

Since there are design patterns I'm less familiar with such as decorators and dependency injection. It's almost the holidays and NestJS decorates everything so it seemed like a good fit. I've used decorators in the past, but it's been a few years. Unless you count React's higher-order components

One goal of the server is to also generate openAPI specs automatically and use those to generate TypeScript types that can be consumed by other repos. In the past I've run into some API interface issues from inconsistent types so I'd like a single source of truth.

NestJS supports automatic validation on the endpoints. In NestJS you define Data Transfer Objects (DTOs) as classes which are used to represent your requests.

That means I can create a class object, use NestJS's class-validator library to add decorators so it looks like this

import { IsString, IsInt, ValidateNested } from 'class-validator'

class Answer {
  @IsString()
  value: string
  @IsString()
  type: 'text' | 'image'
  @IsString()
  exact: boolean
}

export class CreateHuntDto {
  @ValidateNested()
  answer: Answer
  @IsString()
  clue: string
  @IsString()
  description?: string
  @IsInt()
  latitude: number
  @IsInt()
  longitude: number
  @IsString()
  name: string
}

Then use the SwaggerModule to generate an openAPI spec

import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'
...
// This config goes inside the boostrap function in main.ts
const config = new DocumentBuilder()
    .setTitle('Scavenger Hunt API')
    .setDescription('API endpoints for building a scavenger hunt')
    .setVersion('0.1')
    .addTag('scavenger hunt')
    .build();
  const swaggerOptions = {
    autoTagControllers: true,
  };
  const swaggerDocument = SwaggerModule.createDocument(
    app,
    config,
    swaggerOptions,
  );
//   This lets us view the api endpoint when server is running
  SwaggerModule.setup('api', app, swaggerDocument);
//   This saves the spec as a file so we can use it to generate TypeScript next!
  const outputPath = path.resolve(__dirname, 'swagger-spec.json');
  fs.writeFileSync(outputPath, JSON.stringify(swaggerDocument, null, 2));
...

And then we build our types with openapi-typescript. Here's a little snippet:

//src/types/api.d.ts
export interface components {
    schemas: {
        Answer: {
            value: string;
            type: Record<string, never>;
            exact: boolean;
        };
        CreateHuntDto: {
            answer: components["schemas"]["Answer"];
            clue: string;
            description?: string;
            latitude: number;
            longitude: number;
            name: string;
        };

Next steps:

  1. Automatically publish types
  2. Check for breaking changes to spec when opening PR
  3. Load the types in the scavenger hunt repo

I know I know. Where's the AI? I asked Copilot Chat a few things here and there, but I was also reading the documentation on NextJS directly.

"Why?" you may ask? When I asked Copilot some of the above questions Copilot was returning the docs almost verbatim so I just cut out the middleman until I ran into a confusing implementation detail or next step.

A next step like we have in the list above!

Copilot, how can I automatically save my typescript types to an npm types package?

I know I don't have to be so verbose, but it's hard to break that habit.

It seems pretty straightforward. You make a types package inside your package.

mkdir types
cd types
npm init -y

Copilot, how can i automate publishing my types with github actions?

Now I can test pushing my... wait...

I haven't done my initial commit yet. Not a giant deal, but now it's gonna be a bit large for an initial commit and I can't link y'all to the steps as easy. Sorry about that. Let's fix it!