-
Notifications
You must be signed in to change notification settings - Fork 541
Recipe development guideline for the backend and frontend recipes
Rishabh Poddar edited this page Jul 26, 2021
·
10 revisions
- All CRUD operations for all objects in the db (as much as makes sense). For example, if we take email verification as a recipe, we want the user to be able to:
- a) Do the normal email verification flow (by generating the token etc..)
- b) Set if an email is verified or not (without any token)
- c) Know if an email is verified or not
- Should be as decoupled from other recipes as possible so that it can be reused.
- Should be as minimal as possible.
- All sub recipes being used should be allowed to be overrided by an instance provided in the constructor. This will allow super recipes to also use these sub recipes without us creating multiple instances of those sub recipes.
- Recipe interface should follow:
- a) Have one function per CRUD operation
- b) If a function has valid error states, those should be returned from the function like
{status: "OK", ...} | {status: "ERROR_1", ...} | {status: "ERROR_2", ...}
. That is, no throwing or errors unless it's a general error, or the type system for that language allows you to clearly define error types. - c) If the input to the function is a request or response object (as in the case of session recipe), then it should use the abstracted request / response object and not a web framework specific one. Then the web framework specific implementation will override that function which users can then use.
- d) The input to each function should be as minimal as possible.
- API interface should follow:
- a) They should use the abstracted request / response objects and not actually any framework specific objects.
- b) Valid error states should be returned as a 200 response. So the response should look like
{status: "OK", ...} | {status: "ERROR_1", ...} | {status: "ERROR_2", ...}
. - c) The implementation of the API should only use functions from the
input
param, or from other recipes that can be imported by the user without going through the build folder. This allows users to copy / paste API implementations and modify them as per their needs.
- When doing input parsing for APIs, only check for what we care about. If the user has manually added some other fields to the request body, let them go through
- There should be one file where all the functions exposed by this recipe are listed and documented. This file will be linked from the docs as API documentation.
- a) For functions
- b) For API override interface
- c) For function override interface
- Recipe functions should be as isolated as possible.. this means that they should not have to use other recipes (as much as possible). For example, in sign up, we want to first create a new user in the core, and then create a new session. Both of those operations should not happen with one recipe function, and should happen in the api function instead.
- When adding a new web framework support, unless the repo is specifically for that framework, we should not add the framework as a dependency. We can add it as a dev dependency though.
- When creating a recipe that uses two sub recipes, we must design it in a way that users can switch off any of the sub recipes such that it is equavalent to any of the sub (or sub sub recipes). This is done so that in case of multi tenancy, if someone wants a different type of login method per tenant, they can do that all using just one recipe.
- Should be as decoupled from other recipes as possible so that it can be reused.
- Should be as minimal as possible.
- All sub recipes being used should be allowed to be overrided by an instance provided in the constructor. This will allow super recipes to also use these sub recipes without us creating multiple instances of those sub recipes.
- Recipe interface should follow:
- a) If a function has valid error states, those should be returned from the function like
{status: "OK", ...} | {status: "ERROR_1", ...} | {status: "ERROR_2", ...}
. That is, no throwing or errors unless it's a general error, or the type system for that language allows you to clearly define error types. - b) The implementaiton should take care of calling the pre API hook as well as fire the event hook.
- a) If a function has valid error states, those should be returned from the function like
- All components and styles in the recipe should be overridable
- c) All divs etc that are styled, should have a
data-supertokens
and acss
prop like so<div data-supertokens="container" css={styles.container}>
.
- c) All divs etc that are styled, should have a
- Each component theme should have a globally unique name. The format should be {actualRecipeId}{Name of component}. For example
ThirdPartyEmailPasswordSignInButton
- Each theme component must be overridable (using the
withOverride
component). - When doing output parsing for an API call, only handle what we care about. If the user has manually added some other fields to response from the API, let them handle that.
- There should be one file where all the functions exposed by this recipe are listed and documented. This file will be linked from the docs as API documentation.
- a) For functions
- b) For component override interface
- c) For functions override interface
- If the recipe doesn't create it's own user ID and / or uses userIds from another recipe, then it should not assume that the format of the userId is a UUID. See this issue.
- If an "object" can semantically mean the same thing across recipes, then extract that into it's own isolated table. For example, a user is semantically the same across thirdparty and emailpassword recipe. ThirdpartyEmailpassword would want to combine the two for counting and pagination purposes. So we should extract them into its own recipe independent table. See https://github.com/supertokens/supertokens-core/issues/259.
- Foreign key constraint must be isolated to be within the tables of a recipe.
- Store minimal information in a table
- Indexes should be created based on types of expected queries
- Change get count and pagination API specs + tests
- Plugin interface user objects should be directly sent as API response. If you want to hide a property from a response, use the
transient
keyword likepublic transient final String passwordHash;
- APIs in core can interact with other recipes - as long as if the backend SDK repeats certain operations, it's ok. For example, in thirdparty sign up, we also mark the email as verified in the core. We should do the same explicitly in the backend SDK post sign up as well (i.e. call emailVerify recipe function in the backend API). This has two benefits:
- a) If a user is calling just the signUp core function manually, then email verificaiton will be marked as true (based on the input), without any extra function calls.
- b) If a user has overriden the impl of email verification entirely, then our backend API will call that function as well - the core will have this as marked true too, but that is OK as it is ignored).
- About input validation:
- a) User input (like email / password / name etc..) should not be validated in the core, and only in the backend SDK. This is because we provide methods in the backend SDK to override how validation for user facing input works, and if a custom validator is provided, that will not be reflected in the core - which might cause issues.
- b) Input that is generated by the core (like access tokens, UUIDs for auth recipes etc..) can be validated - since the validation logic for those cannot be modified by the user.