Skip to main content
Test Double company logo
Services
Services Overview
Holistic software investment consulting
Software Delivery
Accelerate quality software development
Product Management
Launch modern product orgs
Legacy Modernization
Renovate legacy software systems
DevOps
Scale infrastructure smoothly
Upgrade Rails
Update Rails versions seamlessly
Technical Recruitment
Build tech & product teams
Technical Assessments
Uncover root causes & improvements
Case Studies
Solutions
Accelerate Quality Software
Software Delivery, DevOps, & Product Delivery
Maximize Software Investments
Product Performance, Product Scaling, & Technical Assessments
Future-Proof Innovative Software
Legacy Modernization, Product Transformation, Upgrade Rails, Technical Recruitment
About
About
What's a test double?
Approach
Meeting you where you are
Founder's Story
The origin of our mission
Culture
Culture & Careers
Double Agents decoded
Great Causes
Great code for great causes
EDI
Equity, diversity & inclusion
Insights
All Insights
Hot takes and tips for all things software
Leadership
Bold opinions and insights for tech leaders
Developer
Essential coding tutorials and tools
Product Manager
Practical advice for real-world challenges
Say Hello
Test Double logo
Menu
Services
BackGrid of dots icon
Services Overview
Holistic software investment consulting
Software Delivery
Accelerate quality software development
Product Management
Launch modern product orgs
Legacy Modernization
Renovate legacy software systems
Cycle icon
DevOps
Scale infrastructure smoothly
Upgrade Rails
Update Rails versions seamlessly
Technical Recruitment
Build tech & product teams
Technical Assessments
Uncover root causes & improvements
Case Studies
Solutions
Solutions
Accelerate Quality Software
Software Delivery, DevOps, & Product Delivery
Maximize Software Investments
Product Performance, Product Scaling, & Technical Assessments
Future-Proof Innovative Software
Legacy Modernization, Product Transformation, Upgrade Rails, Technical Recruitment
About
About
About
What's a test double?
Approach
Meeting you where you are
Founder's Story
The origin of our mission
Culture
Culture
Culture & Careers
Double Agents decoded
Great Causes
Great code for great causes
EDI
Equity, diversity & inclusion
Insights
Insights
All Insights
Hot takes and tips for all things software
Leadership
Bold opinions and insights for tech leaders
Developer
Essential coding tutorials and tools
Product Manager
Practical advice for real-world challenges
Say hello
Developers
Developers
Developers
Software tooling & tips

A lesson in type safety with TypeScript, Remix and Prisma

Learn from real-world coding experiments with TypeScript, Remix, and Prisma. Discover how to avoid common pitfalls and improve your type safety skills.
Joseph Lozano
|
November 1, 2022
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

While waiting for Phoenix 1.7 and LiveView 0.18 to drop, I decided to play around with Remix a bit. My experiment was to build a magic link login in Remix, using Prisma as the ORM. I wound up getting burned pretty badly by tricky things in both these libraries that TypeScript was not able to catch for me. I want to share with you some of the lessons I learned while working on this.

loader is a special function in Remix that runs on the client side before your page is rendered. It is similar to getServerSideProps if you are familiar with NextJS, or a controller function in Phoenix or Rails. This code reads the sessionToken from the cookie, and then looks up the user from that sessionToken using Prisma ORM.

export const loader: LoaderFunction = ({ request }) => {
  const session = await getSession(request.headers.get("Cookie"));
  const sessionToken = session.get("sessionToken");
  const user = await getUserForSession(sessionToken);

  return { user };
};

export function getUserForSession(sessionToken: string) {
  const session = await db.session.findFirst({
    where: {
      sessionToken: {
        equals: sessionToken,
      },
    },
    include: {
      user,
    },
  });

  if (session) {
    return session.user;
  } else {
    return null;
  }
}

After I got it working (meaning that I could log in and out from my computer), I decided to make sure the mobile page looked good. I used ngrok to tunnel localhost from my computer and opened the page on my phone.

I was logged in on my phone.

How could this happen? This was an app that I had just written. It was impossible for me to be logged in on my phone.

I’ll give you a moment to see if you can spot the problem.








**spoilers ahead**






There are a few culprits

First, session.get("sessionToken") does not return a string, it returns an any.

Even if I had typed it const sessionToken: string = session.get("sessionToken"), TypeScript would have raised no errors, because string satisfies the type any. Instead, session.get should return unknown, instead of any; since then I would have been forced to cast the type into a string. But this was library code. I could not change the type.

Secondly, the session.get function returns undefined instead of null when the key cannot be found. The patterns for this vary across the TypeScript/JavaScript ecosystem, so I don’t really fault the library code. Nullish types are bad enough that some modern languages do away with them entirely (e.g. Haskell and Rust). JavaScript (and TypeScript) have 2 nullish types.

So, session.get("sessionToken") returns undefined.

No big deal, right?

Well, Prisma treats undefined differently than null. Here’s the documentation.

Prisma Client differentiates between null and undefined:

null is a value undefined means do nothing

So, my where clause (where: { sessionToken: { equals: sessionToken, }, },)) was actually just doing nothing, exactly as Prisma documentation states.

So, what are the lessons we can all learn?

Even though it may seem obvious what a library is doing, and its API is very straightforward, you should always read its documentation. Reading about Prisma’s behavior around null and undefined, rather than just assuming, would have saved me a huge headache.

TypeScript, by a deliberate design choice, has an escape hatch called any. If you (or any library you pull in) uses it, then you can no longer consider your code typesafe. Consider using unknown instead. Inspect the types of any library code you are using, and if they return any, consider casting the result to unknown before handling it.

Related Insights

🔗
A quick guide to TypeScript conditional types
🔗
Get safer TypeScript code with Zod: a practical guide

Explore our insights

See all insights
Developers
Developers
Developers
C# and .NET tools and libraries for the modern developer

C# has a reputation for being used in legacy projects and is not often talked about related to startups or other new business ventures. This article aims to break a few of the myths about .NET and C# and discuss how it has evolved to be a great fit for almost any kind of software.

by
Patrick Coakley
Leadership
Leadership
Leadership
Turning observability into a team strength without a big overhaul

By addressing observability pain points one at a time, we built systems and practices that support rapid troubleshooting and collaboration.

by
Gabriel Côté-Carrier
Developers
Developers
Developers
Why I actually enjoy PR reviews (and you should, too)

PR reviews don't have to be painful. Discover practical, evidence-based approaches that turn code reviews into team-building opportunities while maintaining quality and reducing development friction.

by
Robert Komaromi
Letter art spelling out NEAT

Join the conversation

Technology is a means to an end: answers to very human questions. That’s why we created a community for developers and product managers.

Explore the community
Test Double Executive Leadership Team

Learn about our team

Like what we have to say about building great software and great teams?

Get to know us
Test Double company logo
Improving the way the world builds software.
What we do
Services OverviewSoftware DeliveryProduct ManagementLegacy ModernizationDevOpsUpgrade RailsTechnical RecruitmentTechnical Assessments
Who WE ARE
About UsCulture & CareersGreat CausesEDIOur TeamContact UsNews & AwardsN.E.A.T.
Resources
Case StudiesAll InsightsLeadership InsightsDeveloper InsightsProduct InsightsPairing & Office Hours
NEWSLETTER
Sign up hear about our latest innovations.
Your email has been added!
Oops! Something went wrong while submitting the form.
Standard Ruby badge
614.349.4279hello@testdouble.com
Privacy Policy
© 2020 Test Double. All Rights Reserved.