You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
For this I want data structured differently and isolated in different schemas as tenant virtual DB rather than all the tenants being stored on the default schema (public) and using tenant row-based access,
So far I come up with combinations to result in what seems a solid solution:
defining the schema table to be used by the registered tenant:
Considering this example of the TAX table, covering potential relations zod schema and graphql schema
import*astfrom'drizzle-orm/pg-core';import{pgEnum,pgTable}from'drizzle-orm/pg-core';import{relations}from'drizzle-orm';import{createInsertSchema,createSelectSchema}from'drizzle-zod';import{globProducts}from'@/database/schema/products';import{TAXES_ON,TAXES_CALCULATION,TAXES_TYPES,TaxOn,TaxCalculation,TaxType,}from'@/lib/data/tax';import{tallestElement}from'@/lib/utils';import{z}from'zod';import{timestamps}from'@/database/schema/timestamps';exportconsttaxCalculationEnum=pgEnum('calculation',TAXES_CALCULATION);exportconsttaxTypeEnum=pgEnum('type',TAXES_TYPES);consttaxColumns={id: t.serial().primaryKey(),name: t.varchar('name',{length: 100}).notNull(),code: t.varchar('code',{length: 50}),on: t.varchar('on',{enum: TAXES_ON,length: tallestElement([...TAXES_ON]).length// special case enum: on -> ["sale", "purchase"] | ["sale"] | ["purchase"]}).array().$type<TaxOn>().notNull().default([...TAXES_ON]),calculation: taxCalculationEnum().default('excluded').$type<TaxCalculation>().notNull(),description: t.text(),rate: t.real().notNull(),type: taxTypeEnum().default('percentage').$type<TaxType>().notNull(),
...timestamps,}exportconstglobTax=pgTable('taxes',taxColumns);// this only be used in the zod schemaexportconsttenantTaxes=(tenantSchema: t.PgSchema<string>)=>{consttenantTable=tenantSchema.table('taxes',taxColumns);return{taxes: tenantTable,taxesRelations: relations(tenantTable,({ many })=>({products: many(globProducts,{relationName: 'productTaxes'})})),};};exportconstinsertTaxSchema=createInsertSchema(globTax,{name: z.string().min(3),rate: z.number({message: 'Tax rate is required'}).min(0,'Tax rate should be greater than 0')}).superRefine((data,ctx)=>{if(data.type==='percentage'&&data.rate>100){ctx.addIssue({code: z.ZodIssueCode.custom,path: ['rate'],message:
'Rate should be less than or equal to 100 if the type is percentage'});}})exportconstselectTaxSchema=createSelectSchema(globTax);exporttypeTax=typeofglobTax.$inferSelect;exporttypeNewTax=typeofglobTax.$inferInsert;
Defining a dynamic schema:
Generate a dynamic schema on demand by passing the instance of the PgSchema
exportfunctiongetTenantDB(tenantId: string){constschema=pgSchema(`tenant_${tenantId}`);// Return a new drizzle instance scoped to the tenant's schemareturndrizzle(pool,{schema: tenantSchema(schema),casing: 'camelCase'});}
+Plus: Querying the specific tenant virtual Db
For that i used drizzle with graphql via drizzle-graphql pkg with nextjs app router stack
// app/api/graphql/[tenant]/route.tsimport{getTenantDB}from'@/database';// Assumes getTenantDB fetches tenant-specific DBimport{ApolloServer}from'@apollo/server';import{buildSchema}from'drizzle-graphql';import{startServerAndCreateNextHandler}from'@as-integrations/next';import{NextRequest,NextResponse}from'next/server';import{validateTenant}from'@/lib/tenant';typeTenantParams={params: {tenant: string;};};exportasyncfunctionsetupGraphQLServer(tenantId: string){// Get the tenant-specific database instanceconsttenantDB=getTenantDB(tenantId);// Build the schema using the tenant-specific databaseconst{ schema }=buildSchema(tenantDB);// Configure Apollo Server with the tenant's schemaconstserver=newApolloServer({
schema,includeStacktraceInErrorResponses: process.env.NODE_ENV!=='production',nodeEnv: process.env.NODE_ENV,introspection: process.env.NODE_ENV!=='production',});returnstartServerAndCreateNextHandler(server);}// Handler for the /api/graphql/[tenant] routeasyncfunctionGQLhandler(req: NextRequest,{ params }: TenantParams){const{ tenant }=params;constisValidTenant=awaitvalidateTenant(tenant,req.nextUrl.origin);if(!isValidTenant){returnnewNextResponse('Not Found',{status: 404});}consthandler=awaitsetupGraphQLServer(params.tenant);returnhandler(req);}exportconstGET=GQLhandler;exportconstPOST=GQLhandler;
Now the problem I'm facing is how I can generate migration files for this, if someone can help, or even if there is an alternate approach to use than the one i used (sharing the same goal of virtual tenant DB)
I will be so grateful if you guys dropped your opinion about this.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
-
For this I want data structured differently and isolated in different schemas as tenant virtual DB rather than all the tenants being stored on the default schema (public) and using tenant row-based access,
So far I come up with combinations to result in what seems a solid solution:
defining the schema table to be used by the registered tenant:
Defining a dynamic schema:
Retrieving the tenant virtual DB *(schema):
+Plus: Querying the specific tenant virtual Db
Now the problem I'm facing is how I can generate migration files for this, if someone can help, or even if there is an alternate approach to use than the one i used (sharing the same goal of virtual tenant DB)
I will be so grateful if you guys dropped your opinion about this.
Beta Was this translation helpful? Give feedback.
All reactions