create-t3-app icon indicating copy to clipboard operation
create-t3-app copied to clipboard

feat: extend process.env types as well from envSchemas

Open Nsttt opened this issue 3 years ago • 7 comments

Is your feature request related to a problem? Please describe.

Currently in order to get autocompletion and type-safety there's the clientEnv and serverEnv which we can import to use our environment variables. I don't dislike this approach but I believe they're a better developer experience if we allow to use process.env instead, or at least improve it through extending global types.

Describe the solution you'd like to see

We can probably extend it just like this.

// env.d.ts
import { clientSchema, serverSchema } from "./schema.mjs";
import { z } from 'zod';

declare global {
  namespace NodeJS {
    interface ProcessEnv extends z.infer<typeof clientSchema & typeof serverSchema> {}
  }
}

Desribe alternate solutions

Due to separation of concerns in NextJS server and client variables this might not be the best approach, but rather an alternative to import env variables from files.

Not really sure how can we achieve the same with process.env but if someone have any idea that would be a plus.

Additional information

No response

Nsttt avatar Sep 06 '22 09:09 Nsttt

I like the idea of this as I think many users are probably just ignoring the typesafe env vars and using process.env instead because that's what they're used to. This way type safety would be the default.

I tried your code snippet and didn't get autocomplete on environment variables. Do you have a working sample app where you implemented this?

c-ehrlich avatar Sep 06 '22 13:09 c-ehrlich

I tried your code snippet and didn't get autocomplete on environment variables. Do you have a working sample app where you implemented this?

Sure, it worked just fine for me here with this repo. Just a brand new t3-app that has that file.

Then I can see the autocomplete. image

Nsttt avatar Sep 06 '22 21:09 Nsttt

Can confirm this works, not sure I was doing wrong earlier.

Looks great. Do you want to open a PR for it? I think it should be reviewed by as many people as possible because this is such a core part of the app, but if it works without caveats then I think it's a big win.

c-ehrlich avatar Sep 06 '22 21:09 c-ehrlich

I think it should be tested a bit more. Totally open to make a PR about it. But for example in that specific example, we can see that NEXTAUTH_URL still has the string | undefined type. Probably due to checking it with z.string().url().

I'll give it a look tomorrow a bit more and I'll open a PR or update what I find here. 😄

Nsttt avatar Sep 06 '22 21:09 Nsttt

Another downside I can see is that you get autocomplete on all env variables everywhere, even though they don't exist everywhere (ie non-NEXT_PUBLIC in the frontend). This is a nice thing that was solved by splitting it into server and client.

But the existing schema would still remain of course.

Assuming there are no other major issues, is type safety and autocomplete on the more familiar process.env worth the confusion/overhead of having two different things? I'm leaning yes but would be interested in other opinions.

c-ehrlich avatar Sep 06 '22 22:09 c-ehrlich

Won't work for things that are using .tranform().

With z.input<x> it could be quite nice DX but the file isn't guaranteed to have been loaded which means it's also not guaranteed to be correct

KATT avatar Sep 07 '22 22:09 KATT

I wonder, shouldn't that be taken care by the files being imported inside next.config.js ?

Nsttt avatar Sep 09 '22 15:09 Nsttt

Oh man, I've sure let this one slip over my todos. Any chance we can give this one another look ?

Nsttt avatar Dec 23 '22 18:12 Nsttt

Some updates on this.

Currently there's on issue regarding new env vars introduced into our serverSchema. If we extend our Process.env with a declaration file like this one.

// env.d.ts
import type { clientSchema, serverSchema } from "./schema.mjs";
import type { z } from "zod";

declare global {
  namespace NodeJS {
    // eslint-disable-next-line @typescript-eslint/no-empty-interface
    interface ProcessEnv
      extends z.infer<typeof clientSchema & typeof serverSchema> {}
  }
}

There's a issue on our serverSchema variable due to apparently 'serverSchema' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.

export const serverSchema = z.object({
  NEXTAUTH_SECRET:
    process.env.NODE_ENV === "production" // <-- THIS BREAKS
      ? z.string().min(1)
      : z.string().min(1).optional(),
  NEXTAUTH_URL: z.preprocess(
    // This makes Vercel deployments not fail if you don't set NEXTAUTH_URL
    // Since NextAuth.js automatically uses the VERCEL_URL if present.
    (str) => process.env.VERCEL_URL ?? str, // <-- THIS BREAKS.
    // VERCEL_URL doesn't include `https` so it cant be validated as a URL
    process.env.VERCEL ? z.string() : z.string().url() // <-- THIS BREAKS.
  ),
});

Weirdly enough, If I remove these lines and leave the variable without NEXTAUTH_SECRET and NEXTAUTH_URL they still appear on the process.env type signature !

CleanShot 2022-12-31 at 13 32 27@2x

If anyone can also try it out on their machine with the following steps It'd be much appreciated.

  1. Bootstrap a new ct3a project.
  2. Create a env.d.ts files inside the env folder with the contents above.
  3. Test the process.env type signature on any TS file.

Nsttt avatar Dec 31 '22 12:12 Nsttt

What are your guy's thoughts on this implementation? https://github.com/JacobADevore/next-validenv

JacobADevore avatar Jan 16 '23 03:01 JacobADevore

I'm still getting error even after adding the interface extends

src/env.ts:16:15 - error TS2411: Property 'PORT' of type 'number' is not assignable to 'string' index type 'string | undefined'.

16     interface ProcessEnv extends Env {}
                 ~~~~~~~~~~

SwapnilSoni1999 avatar Feb 09 '24 23:02 SwapnilSoni1999