4 ms·
Thats very cool! I'm also massively in favour of having the db as the source of truth. When I first started looking into building my own SaaS I actually tried
by yasserf 5y ago
Thats very cool!
I'm also massively in favour of having the db as the source of truth. When I first started looking into building my own SaaS I actually tried to go down the database first design, where most of your functionality runs within postgres triggers. But it wasn't near as easy to do things and writing SQL logic is much harder to test.
Figured the next best thing is to just tie the database in extremely closely using typescript into the backend (I rarely define custom types, always try to use Pick/Omit/Partial combinations all the way to the client SDKs).
I ended building generating forms based on the meta data in postgres (select fields know the values from enums and multiselect from enum arrays), so if you define a table in postgres, you just specify which fields you want to render (out of the available columns) and in what order and it pretty much gives you a UI form that is directly rendered in react. Works really well when you have a form component library at your disposal.
- random_kris 5y agoYou have any of this on github?
- yasserf 5y agoThe form building part I don't no, I built it for a client that has a few dozen forms with hundreds of fields / options which is hard to keep up with. So we settled on using a csv file that is essentially a products owner way of saying, i want this field on this entity of this type. For example: entity,field,type,constraints delivery,arrived_at,Date,{ before: 0 } And in the form: form,field_1,field_2,... delivery_details,left_at,arrived_at,... Our CI then checks that the field actually exists in the typescript schema, and if not the build fails on our development environment (and suggests the basic SQL to fix it). The company is small enough for us to be able copy the sql into a migration script and let it run without skipping a beat. It's not a magical pill, renaming, deleting and custom migrations need to be dealt with manually. But for the most part 80%_of our changes are additions and this way it just works.
- yasserf 5y agoI am looking to release the setup I use for deployment but tbh it isn't tested (via tests, since I rely on typescript for most of my data shuttling) and there's alot to it so need to write alot of documentation which i don't have time for. But, the jist is, we APIs like this (this can be improved with less generic json schemas): export const updateSamf: APIFunction<UpdateSamf, void> = async ({ database }, { samfId, ...data }) => { if (Object.keys(data).length === 0) { throw new InvalidParametersError('No fields to update') } if (data.title && data.title.length < 3) { throw new InvalidParametersError('Title needs to be at least 3 characters') } if (data.tags) { data.tags = data.tags.map((t) => t.toLowerCase()) } await database.crudUpdate<Samf>('samf', data, { samfId }, new SamfNotFoundError()) if (data.cover && previousCover) { await content.delete(previousCover) } } export const routes: APIRoutes = [{ type: 'patch', route: StudioRoutePath.SAMF_CRUD, func: updateSamf, schema: 'UpdateSamf', permissions: isSamfOwner }]