Skip to main content

01. Grammar

TypeScript Cheatsheet

I think types gave me a lot of trouble when I first started learning TypeScript too. It was infuriating.

Blogs described types in all sorts of ways like the examples below, and I kept wondering, "So which one is correct?" Rather than looking for one correct answer, it is fine to regard the form that looks cleanest to you as the answer.

As time passes, what looks clean now may stop looking clean; you may start weighing performance benefits or come to prefer nested forms. Starting with whichever style suits your taste is better for your peace of mind.

When should Export / Export default be used?

  • export?

    • Add export when you want to use a function, type, variable, and so on from another file.
  • export default?

    • import EEE from 'file.ts', import { EEE } from 'file.ts' The difference between these two lines is whether they use {}. You can omit {} only when something has been marked export default.
  • The difference? callee.ts (commonly called the receiving side)

    const WHO_AM_I = 'default_var';
    export const XXX = 'im_not_default';
    export default WHO_AM_I;

    caller.ts (the calling side)

    import XXX from 'callee.ts';

    console.log(XXX);
    -> The result is 'default_var'

    caller2.ts (caller 2)

    import { XXX } from 'callee.ts';
    console.log(XXX)
    -> The result is 'im_not_default'

When and where should you add export!?

-> When: as in caller.ts, when another file (the caller) needs to use a variable, class, or type in the current file (the callee).

Where: add export before a variable, type, class, or interface such as type, interface, class, const, let, or var on the receiving side.


About types

type JSONResponse = {
version: number; // Primitive field
/** In Bytes */ // Field descriptions can be written here
payloadSize: number; // Primitive field
outOfStock?: boolean; // Optional field
update: (retryTimes: number) -=> void; // Arrow function
update(retryTimes: number): void; // Function
(): JSONResponse // Can be called as JSONResponse()
[key: string]: number; // Allow any index
new (s: string): JSONSResponse; // Newable
readonly body: string; // Read-only element
}

Example 1) Request and return values

const requestBody = {
username: 'testuser',
password: 'testpassword',
type: 'user', // Available types are 'user', 'admin', and 'superadmin'
}


const responseBody = {
data: {
'access_token': 'asdjfklajsldkfjklasjdf.skdfsdf.asdfsdf',
}, // For an error, respond with data: null
msg: 'Login successful.',
cd: 202, // cd can be 20X, 30X, 40X, or 50X
// err: 'Return an error message on error' or ['error message 1','error message 2']
// When cd is 40X or 50X, an err field is also present and may be a string or an array of strings
}

Given request < - > response values like the above, they can be represented with types/interfaces as follows. (In fact, my preferences gradually changed from top to bottom.)

  • Type representation (Nested)

    type RequestBody = {
    username: string;
    password: string;
    type: "user" | "admin" | "superadmin"; // Specify strings directly as types to prevent other values in advance
    }

    type ResponseBody = {
    data: {
    access_token: string;
    } | null;
    // Receive ResponseData for a normal response and null for an error
    msg: string;
    cd: number;
    err?: string | string[];
    }

  • Type representation (Non-Nested)

    type RequestUserType = "user" | "admin" | "superadmin"; // Specify strings directly as types to prevent other values in advance
    type RequestBody = {
    username: string;
    password: string;
    type: RequestUserType
    }

    type ResponseData = {
    "access_token": string;
    }

    type ResponseBody = {
    data: ResponseData | null; // Receive ResponseData for a normal response and null for an error
    msg: string;
    cd: number;
    err?: string | string[];
    }

  • Interface representation (Nested)

    interface RequestBody {
    username: strign;
    password: string;
    type: "user" | "admin" | "superadmin";
    }

    interface ResponseBody {
    data: {
    access_token: string;
    } | null;
    // Receive ResponseData for a normal response and null for an error
    msg: string;
    cd: number;
    err?: string | string[];
    }

  • Hybrid (Non-Nested)

    type RequestUserType = "user" | "admin" | "superadmin"; // The type part alone can be separated for use

    interface RequestBody = {
    username: string;
    password: string;
    type: RequestUserType;
    }

    type ResponseData = {
    "access_token": string;
    }

    interface ResponseBody = {
    data: ResponseData | null; // Receive ResponseData for a normal response and null for an error
    msg: string;
    cd: number;
    err?: string | string[];
    }

Normalizing API responses

Server-side code for response normalization (reference)

import { HttpStatus } from "@nestjs/common";

export { HttpStatus };

export type RecordTypeReturn = RetType<Record<string, any>>;

interface ReturnType {
cd: number;
data?: T;
err?: string;
msg?: string;
ext?: object;
}

// Shared response structure
export class RetType<T> extends ReturnType {
// * ... implementation omitted * //
getBody(): object {
return {
...this.data,
msg: this.msg,
ext: this.ext,
};
}
}


// Error interceptor

if (err instanceof HTTPErr) { // When err is an instance of HTTPErr
if (err instanceof RetType) // When err is an instance of RetType<any>
{
err = res as string;
msg = (res as string) !== exception.message ? exception.message : '';
ext = {};
} else if (exception instanceof KError) { // When it is an instance of KError
err = isString(res) ? res : res.error || exception.message || '';
msg = isString(res) ? res : res.message || exception.message || '';
ext = exception.getExtraInfo();
} else {
err = exception.message || 'Unknown Error';
msg = res.message || '';
ext = {};
}
} else {
Logger.error(`[GlobalExceptionFilter] - An error other than HttpException occurred`);
Logger.error(exception);
err = 'Unknown Error';
msg = "I'm a Tea Pot. Look at Console";
ext = {};
}
// msg: attempt conversion to string; returned as string | string[]
// err: attempt conversion to string; generally returned as string

const exceptionExtraInfo: object = Object.assign(ext, {
timestamp: convertPrettyKST(new Date()),
path: new URL(request.url, `http://${request.headers.host}`).pathname,
});
// ext information + { timestamp: current time, path: request URL }

const responseBody: RetType<RecordTypeReturn> =
new RetType<RecordTypeReturn>()
.setCode(status)
.setErr(err)
.setMsg(msg)
.setExt(exceptionExtraInfo);

Skimming through the abbreviated code above gives this form:

type GenericResponse = {
err?: string;
msg?: string | string[];
ext?: {
timestamp?: string | number;
path?: string;
},
}
// Shared parts can be grouped together like this

If you want to use it for a message such as { err: 'An error occurred.', xxx: 'not allowed' }:

type ExampleOneResponse = GenericResponse & { xxx: string };
// Attach { xxx: string } to the GenericResponse type

// Or

interface IExampleOneResponse extends GenericResponse {
xxx: string;
}

// This form can also be used