Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable users to define their env var validations #2363

Merged
merged 19 commits into from
Jan 10, 2025

Conversation

infomiho
Copy link
Contributor

@infomiho infomiho commented Oct 30, 2024

The idea

Wasp uses Zod to define its validation rules for the env variables it expects e.g. it defines that JWT_SECRET is required and a string. After this validation passes, Zod gives us a validated object that we can safely use knowing that e.g. JWT_SECRET is present and it is a string:

import { env } from 'wasp/server/env'

const jwtSecret: string = env.JWT_SECRET

Users need their own user-land env vars e.g. STRIPE_API_KEY. Some users validate their env vars with manual checks to make sure that the env var is set before running the app e.g.

if (!process.env.STRIPE_API_KEY) {
  throw new Error('STRIPE_API_KEY is missing');
}

Since Wasp already uses Zod to validate env vars, we want to expose the same mechanism to our users. There are two main benefits:

  1. Users don't have to come up with their own validation mechanisms
  2. Users can use the same env object for both Wasp defined env vars and their own user-land env vars

How this works

Users need to define their env vars validation by defining a Zod object schema. They then declare that in the Wasp file. Something like this:

// env.ts
import * as z from 'zod'
import { defineEnvValidationSchema } from 'wasp/env'

export const serverEnvValidationSchema = defineEnvValidationSchema(
  z.object({
    STRIPE_API_KEY: z.string({
      required_error: 'STRIPE_API_KEY is required.',
    }),
  })
)

export const clientEnvValidationSchema = defineEnvValidationSchema(
  z.object({
    REACT_APP_NAME: z.string().default('TODO App'),
  })
)

and then:

server: {
  ...
  envValidationSchema: import { serverEnvValidationSchema } from "@src/env",
},
client: {
  ...
  envValidationSchema: import { clientEnvValidationSchema } from "@src/env",
},

Wasp merges this user defined schema with its built-in env vars validation schema when it validates the process.env object on the server and the import.meta.env object on the client.

Now users can use the env object to access their env vars like this:

import { env } from 'wasp/server/env'

const stripeApiKey: string = env.STRIPE_API_KEY

Caveats

  • users will need to be careful not to cause circular imports. Ideally they use a file only for env schemas e.g. env.ts
  • maybe this will clash with Build time validation for client env #2392 (there will be conflicts that we'll resolve)

@infomiho infomiho mentioned this pull request Nov 26, 2024
5 tasks
@infomiho infomiho marked this pull request as ready for review December 18, 2024 09:34
@Martinsos
Copy link
Member

Caveats:

  • users will need to be careful not to cause circular imports. Ideally they use a file only for env schemas e.g. env.ts
  • user will need to use the EnvValidationFn with satisfies
  • maybe this will clash with Build time validation for client env #2392

@infomiho could you please describe how this works, what is the experience for the user? I see there are no docs yet, and it would help me with reviewing so I don't have to guess it from the code + so I can consider if code does indeed do what the idea was.

@infomiho
Copy link
Contributor Author

infomiho commented Jan 3, 2025

@Martinsos I've updated the PR description to give you a proper intro into the task I was tackling

@Martinsos
Copy link
Member

@infomiho from the high level / DX perspective, sounds good to me, thanks for the detailed description! If they define the same var as Wasp already defines, what happens? Conflict or override or nothing? What do we want to happen, is there any benefit in them being able to override Wasp's var definitions? Maybe conflict is the best?

@infomiho
Copy link
Contributor Author

infomiho commented Jan 8, 2025

@Martinsos right now, since we merge their schema into ours, they can override our rules: example how that works

If their validation rules are less strict or wrong, they will get a type error (change JWT_SECRET to number) or even a runtime error (make something optional and then it fails in the runtime).

We can merge our schema into theirs, therefore our rules would always win - that seems okay to me, even good enough.

I think, we could go through their schema keys and our schema keys and if they are conflicts, output a custom error. But this is something I would need to check.

@infomiho infomiho requested a review from sodic January 8, 2025 12:10
Copy link
Contributor

@sodic sodic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work!

Two main requests: the public API change and updating the TS SDK.

What was the naming problem you mentioned?

waspc/data/Generator/templates/sdk/wasp/env/index.ts Outdated Show resolved Hide resolved
waspc/data/Generator/templates/sdk/wasp/server/env.ts Outdated Show resolved Hide resolved
waspc/examples/todoApp/prettier.config.cjs Outdated Show resolved Hide resolved
waspc/src/Wasp/AppSpec/App/Client.hs Outdated Show resolved Hide resolved
@@ -72,6 +71,11 @@ data-files:
packages/studio/dist/**/*.css
packages/studio/package.json
packages/studio/package-lock.json
packages/wasp-config/dist/**/*.js
Copy link
Contributor Author

@infomiho infomiho Jan 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sodic I've added this change here so I could cabal install and test things locally. I can make a separate PR if you want

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can stay here 👍

@infomiho infomiho requested a review from sodic January 10, 2025 09:34
Copy link
Contributor

@sodic sodic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noice

@@ -72,6 +71,11 @@ data-files:
packages/studio/dist/**/*.css
packages/studio/package.json
packages/studio/package-lock.json
packages/wasp-config/dist/**/*.js
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can stay here 👍

[ "defaultServerUrl" .= Server.defaultDevServerUrl,
"envValidationSchema" .= GJI.jsImportToImportJson (extImportToJsImport <$> maybeEnvValidationSchema)
]
maybeEnvValidationSchema = AS.App.client app >>= AS.App.Client.envValidationSchema
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd maybe call this maybeJsImport and put more logic from line 66 here.

Same applies to the server function

@infomiho infomiho merged commit ae51b4c into miho-zod-env Jan 10, 2025
6 checks passed
@infomiho infomiho deleted the miho-zod-user-env branch January 10, 2025 16:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants