Skip to content
Nate
Stephens

ALL POSTS

All of my published content

AI Assisted Coding

Some examples covering versatile applications of AI in coding, ranging from project ideation to deployment assistance. It introduces concepts like generating code using pseudocode, creating edge cases, debugging, code conversion, documentation, and studying web development. The examples provided showcase the broad spectrum of tasks AI can assist with, making it a comprehensive guide for leveraging AI in various coding scenarios.

Prompt Engineering

Key elements for effective AI prompts, highlighting specificity, technical terms, context provision, and iterative refinement. It suggests controlling response format, breaking down tasks, and optimizing prompts. Role-based prompting is introduced for tailored assistance, and the importance of providing examples in prompts is emphasized across various scenarios.

PostgreSQL Foreign Keys

If you're going to have two tables reference each other, use foreign keys where possible. It forces useful constraints to make sure delete and update behaviors are intentional and it makes the queries faster.

PostgreSQL DB Connection and Security

A guide to connecting to a PostgreSQL database using the pg package in Node.js, explaining the differences between a client and a pool of clients. Also emphasizes the importance of parameterizing queries and sanitizing user inputs to prevent SQL injection attacks.

PostgreSQL CRUD Commands

A brief overview of common SQL commands used for interaction with PostgreSQL. It explains how to insert single and multiple records, perform upsert operations, update and delete records, and select specific columns.

PostgreSQL Functions and Operators

PostgreSQL provides a large number of functions and operators for the built-in data types. Here I briefly go over LIKE and ILIKE, two symbols used for character matching, and the CONCAT function. These all allow for basic fuzzy searching of your data.

PostgreSQL Databases and Tables

Here is a quick guide on how to run PostgreSQL in Docker. It further explains how to create a database, tables, and records, as well as how to drop and alter tables. It also provides examples of psql commands and explains how to use them.

Semantic HTML - Content Sectioning

Content sectioning elements allow you to organize the document content into logical pieces. Use the sectioning elements to create a broad outline for your page content, including header and footer navigation, and heading elements to identify sections of content.

Reusable Components with Tailwind - Examples

I'm a recent convert to Tailwind CSS after using styled-components for years. Tailwind has a different approach to reusing styles, but acknowledges that creating reusable components makes sense for certain use cases. Here I will demonstrate 3 examples of a reusable component, created with React and TypeScript, and styled with Tailwind utility classes using props, clsx, and cva, respectively.

Opacity with CSS Variable Colors in Tailwind

There's a more flexible way to customize your color palette in Tailwind CSS beyond the advice given in their documentation. You can already define custom colors using CSS variables in Tailwind while preserving the opacity modifier syntax, however this method also allows you to easily use the built-in Tailwind colors.

Dynamic Class Names in Tailwind

Tailwind will only find classes that exist as complete unbroken strings in your source files. If you use string interpolation or concatenate partial class names together, Tailwind will not find them and therefore will not generate the corresponding CSS

Objects Are Not Really Nested

Objects and arrays in JavaScript do not truly contain other objects or arrays. Instead, objects and arrays contain references to other objects or arrays. When nested objects or arrays are updated, the original object or array is updated as well. To avoid unintended mutations, you should create copies of nested objects or arrays and update the copy instead of the original.

Typing a Data Layer - Example

An example of typing a data layer. This code defines a TypeScript interface EntityMap that acts as a type lookup table. It maps string keys (e.g. "book", "movie", "song") to class instances. There is also a Store class, which provides a generic CRUD API for accessing and managing instances of these classes.

Filtering Properties by Value

Here's a detailed explanation of how to filter properties based on the value type in TypeScript using the built-in `Pick` utility. There's an example of hardcoded filtering using the Document object and a more generic version using type parameters. The explanation breaks down the steps of filtering using mapped index types, conditional types, never, index access types, and the intersection operator.

Mapped Index Types

TypeScript allows you to define new types based on existing ones using Mapped Types and Indexed Access Types. The mapped type creates a new type by looping over a set of keys and determining the value type for each key based on the original type. The built-in `Pick` type utility allows you to select a set of properties from an original type and use them to create a new type. Additionally, TypeScript provides three utility types `Partial`, `Required`, and `Readonly` to modify properties in a type as optional, required, or readonly respectively. You can also define your own utility types for specific use cases.

Template Literal Types

Template literal types are a way to create a new type in TypeScript by using the same syntax as JavaScript template literals, but in a type expression rather than a value expression. Types can be created by combining two union types of string literals. TypeScript also provides utility types such as `UpperCase`, `LowerCase`, `Capitalize`, and `Uncapitalize` that can be used within the template literal types. An example of using a template literal type would be creating a custom type to represent setter methods of a data state object.

Indexed Access Types

Indexed Access Types are a way to retrieve specific property types from another type in TypeScript. They allow for accessing parts of a type by using a property key or index, making it easy to extract specific parts of an object or array.

Mapped Types

Mapped Types are used to specify a specific subset of keys and allow us to transform types in a structured and deliberate way. Mapped Types are similar to the map function for arrays. The TypeScript utility Record is an example of a mapped type.

Inference with Conditional Types

Conditional types in TypeScript can be used with the `infer` keyword to extract type information from other types. The `infer` keyword is used within the condition expression of a conditional type and can only be used in that context. An example use case of `infer` is to determine the type of the first argument of a class constructor.

Conditional Types

Conditional types use a ternary syntax to express types, where the condition determines the type. The use of the `extends` keyword acts as a minimum requirement or a generic constraint, similar to a `>=` comparison.

Extract and Exclude Utilities

The `Extract` and `Exclude` utility types in TypeScript are used to obtain sub-parts of a type. The `Extract` type is used to get a sub-part of a type that is assignable to some other type, while the `Exclude` type is used to obtain a sub-part of a type that is not assignable to some other type. Both types are implemented using conditional types. They are useful for refining and narrowing down the properties of complex types in TypeScript.

Type Queries

The keywords `keyof` and `typeof` are type queries in TypeScript that allow you to obtain type information from values. `keyof` is used to obtain types representing all property keys on a given interface. `typeof` is used to extract a type from a value. When used with classes, `typeof` is commonly used to obtain a type representing the constructor (or static side) of a class.

Classes and TypeScript

For classes, the TypeScript programming language provides three access modifier keywords to describe who should be able to see and use class fields and methods. The keywords are `public`, `protected`, and `private`. Public allows access to instances of the class and subclasses, protected allows access only within the class and subclasses, and private only allows access within the class itself. Additionally, the `readonly` keyword can be used to prevent reassignment of a property and the param properties provide a more concise syntax for class definition.

Generic Constraints and Scope

Generic constraints allow for a minimum requirement to be set for type parameters, providing a high level of flexibility while ensuring minimal structure and behavior. Type parameters in TypeScript work similarly to function parameters, where inner scopes can access outer scopes, but not vice versa.

Generics

Generics are a way of defining types in terms of other types in TypeScript. They are type parameters and are used to establish a relationship between the type of thing being passed to a function and the type of thing returned from the function. Generics are similar to function arguments, but for types, and their type will change based on the type parameters provided.

Nullish Values

In TypeScript the nullish values are `null`, `undefined`, `void`, and the non-null assertion operator `!`. `null` means there is a value and that value is nothing, while `undefined` means the value isn't available (yet?). `void` is used to describe that a function's return value should be ignored. Finally, the non-null assertion operator (!) is used to cast away the possibility that a value might be null or undefined, but it is not recommended to use it.

Top and Bottom Types

The Top Types in TypeScript describe a set of values that can be any possible value allowed by the type system. The two main Top Types are `any` and `unknown`. `any` can accept any value but may cause runtime errors. `unknown` can accept any value but requires a type guard and is considered a safer option for values received at runtime. The Bottom Type is `never`, which describes no possible value allowed by the type system and is typically used for exhaustive conditionals where all possible types have been checked and the only remaining type is `never`.

Type Guards and Narrowing

Type guards are used in TypeScript to determine the type of a value and narrow it down to a more specific type based on certain conditions. There are built-in type guards like `instanceof`, `typeof`, and `Array.isArray()`. User-defined type guards can be created with the `is` operator, which is used in a type guard function with a return type of `valueToTest is Type`. To write high-quality type guards, it is important to be careful when checking for falsey values as it could lead to incorrect type castings.

Function Overloads

Function Overloads are a solution in TypeScript for resolving similar issues as Discriminated Unions. The idea is to have the type of one parameter determine the type of another parameter in a function. This is done by declaring multiple "heads" that define an argument list and return type, followed by the implementation. The implementation must be general enough to include all the possibilities from the "heads".

Function Types

There are two different approaches to defining function types in TypeScript. The first approach uses a type alias to specify the input and return types of a function, while the second approach uses an interface. Also note the `void` type, which is used to describe the return type of a function whose return value is intended to be ignored. Setting a function's return type to `void` can help prevent errors.

JSON Types

A JSON type represents any allowable JSON value, which must be an object, array, number, string, boolean, or one of the literal names false, true, or null. It cannot be a function, undefined, BigInt, or class. The type is defined using recursive types.

Type Aliases and Interfaces

Type aliases can only be declared once within a scope and can hold any type. Interfaces, on the other hand, are used to define object types and can be extended or implemented by classes. TypeScript interfaces are open, meaning multiple declarations can be made in the same scope and are merged together. It's best to use a type alias if you need to define something other than an object type and an interface if you need to define an object type that can be extended or implemented by a class.

Union Types

Union types are described using the `|` (pipe) operator and can be thought of as the logical boolean operator `OR`. The possible overlap of types in the union is the shared properties of all objects in the union. Here we explain how to use type guards and discriminated unions to determine the exact type of a value in a union type. These guards use expressions or a property of an object to identify the specific type.

Excess Property Checking

TypeScript helps detect an issue in the use of object literals by checking for excess properties in comparison to a defined type. If an object literal is used as an input to a function, TypeScript throws an error if there are any excess properties. The error can be fixed by either removing the property, adding the property to the function argument type or passing the object as a variable.

Function Return Types

It can be important to explicitly declare function return types in TypeScript. The return type declaration helps TypeScript verify that the implementation matches the expectation and improves error notification by surfacing errors where the function is defined rather than where it is invoked. It's best to be explicit about the return type to avoid issues with possibly undefined values.

Index Signatures

When accessing properties from an object typed with an index signature, we need to be careful as the index signature allows us to assume any property and provides it with type completions, even if the constant phones has been assigned to an empty object. A safer implementation would be to first check if the property is a defined property on the object by using an if statement or the optional chaining operator.

Optional Properties

In TypeScript, optional properties can be left out and will have a type of `undefined`. This is only true if the property is marked as optional with a `?` in the type definition. If the property is not marked as optional, it must be included in the object passed to a function and cannot be left out. The type of an optional property is more than just `type | undefined`.

Tuples

A tuple in TypeScript is a data structure that consists of an ordered list of elements with a specific type. To declare a tuple, you need to explicitly state the type of each element in the list. As of TypeScript 4.3, the support for enforcing tuple length constraints is limited.

Type Inference and Literal Types

The basics of TypeScript's handling of type inference and literal types. Using "let" infers a general type, while "const" infers a specific, immutable literal type. Objects properties are non-literal by default, but can be made literal with "as const".

Type Systems

Type checking is a task that evaluates compatibility or type equivalence between a function's input and a provided argument. It also applies to variable assignments and return types. TypeScript uses a structural type system, meaning that it only cares about the shape or structure of the argument, not its origin. TypeScript is also a static type system, meaning it performs type checking at compile time, while dynamic type systems perform type checking at runtime.