Trailbase: Fast, single-file, open-source app server built using Rust and SQLite
github.com
github.com
Is the string interpolation straight into the sql from the query string in your getting started docs (https://trailbase.io/getting-started/first-ui-app) safe? It smells a bit.
From a quick look (on mobile) I can see that the function takes parameters, but you're not using that.
The fun aspects of this are that:
(a) I could actually imagine trailbase.js contents that would make it not SQL injection: you could have parsePath(…).query.get(…) return objects with a toString() that escaped SQL. This would raise even more questions, and I was sure it wouldn’t be the case, but it’s possible.
(b) You could make it work safely, converting interpolation into parameters, by using a tagged template string. This could require only a tiny change:
return await query(
sql`SELECT Owner, Aroma, Flavor, Acidity, Sweetness
FROM coffee
ORDER BY vec_distance_L2(
embedding, '[${+aroma}, ${+flavor}, ${+acid}, ${+sweet}]')
LIMIT 100`
);
(You could even make it query`…`, but I think query(sql`…`) is probably wiser. As for the plusses I put in there, that’s to convert from strings to numbers.)This is a concept that’s definitely been done seriously. The first search result I found: https://github.com/blakeembrey/sql-template-tag.
probably something like this would work then too (didn't test):
return await query(
`SELECT Owner, Aroma, Flavor, Acidity, Sweetness
FROM coffee
ORDER BY vec_distance_L2(
embedding, '[?,?,?,?]')
LIMIT 100`,
[+aroma, +flavor, +acid, +sweet],
);
it is nice because the query string is constant then and a prepared query could be cached..And actually on second consideration, you probably can’t use binding parameters here: you’re trying to slot numbers in as JSON values inside a string! Maybe you’d have to write vec_distance_L2(embedding, ?), with parameter JSON.stringify([+aroma, +flavor, +acid, +sweet]).
—⁂—
const [data, setData] = useState<Array<Array<object>> | undefined>();
That type stinks. Kill the undefined bit by giving it an initial value, then you can skip the `?? []` later too; and replace Array<object> with the actual row type, probably naming it, maybe like this: type Record = [string, number, number, number, number];
const [data, setData] = useState<Record[]>([]);
—⁂— const params = Object.entries({ aroma, flavor, acidity, sweetness })
.map(([k, v]) => `${k}=${v}`)
.join("&");
No need to construct the query string manually: const params = new URLSearchParams({ aroma, flavor, acidity, sweetness });
I confess I’m puzzled about this one too, as trailbase.js suggests you know about URLSearchParams, which is what you should use for all typical query string manipulation, parsing and generation.Mucking about with SQL strings, to me, is akin to writing your own crypto: Don't. Unless you must (because, say, it doesn't exist yet.) Doing this is asking for security problems later. Trust that the SQLite team (or any other SQL engine) have more experience and have provided a correct interface. And if that interface isn't correct, don't just run off an make your own- contribute a fix so we can keep all the lessons learned together.
Beside, it states that it is so fast that there's no need for cache, and supports SQLite only. So it seems to target only very simple applications, eg. straightforward relational DB <-> Json Rest/HTTP.
Could you expand a bit on what more advanced capabilities you're missing for anything beyond a very simple app? I'm genuinely curious but also see SQLite often undersold
No denying SQLite is very capable. However in my understanding it's designed for being used by a single application, it's not really scalable, and its functionalities are still limited compared to the big DBs. Anyway there's no single fit for all cases, and for core components flexibility is really important.
> retrieval of data from third party providers can take long so you'll want to cache results one way or another.
Agreed. Caching of slow external sources will always be beneficial (at least if you need it more than once :) ). You should use whatever makes the most sense. The argument is more that data from TB is already pretty quick, quick enough that you could even use it as a cache.
> However in my understanding it's designed for being used by a single application,
SQLite by design allows for concurrent writes and parallel reads from any number of processes.
> it's not really scalable,
What do you want to scale to? Postgres is fairly similar in terms of scalability, i.e. master writes + read replicas. Once you're going beyond that scale you get a lot more benefit out of more specialized, problem-oriented solutions.
> and its functionalities are still limited compared to the big DBs.
It full SQL and it's extensible. Depends on what you need. It certainly doesn't have the same rich ecosystem or flexibility of posgres where you can even swap out the storage engine. I was mostly curious, if there's some specific functionality you're missing.
> and for core components flexibility is really important.
Agreed: the right kind of flexibility for your problem.
I'll play devils advocate here: imagine you'd start to deeply depend on one specific postgres plugin and then that plugin get discontinued or you're hitting performance bottlenecks and you're to locked-in to move to a more specialized solution. Don't take this too seriously, I just wanna say that there's a balance.
https://trailbase.io/comparison/pocketbase/ https://trailbase.io/comparison/supabase/
nice work OP - i like this version of tech where we all get along. the project looks great, good luck!
Whether to use docker or not is really up to you. It's just a way to deploy other assets, e.g. a static html bundle. More importantly it saves me the trouble of providing windows builds at this current time :)
[1]: https://learn.microsoft.com/en-us/dotnet/core/deploying/sing...
[2]: https://nodejs.org/api/single-executable-applications.html
I think it's meant for people who want to build a front end without building a backend.
supabase team here. Yes, we offer an auto-generated API (using PostgREST). But users can also connect directly to their Postgres database or through a connection pooler. People have preferences, so we offer options.
Also, what features do you need to code in JavaScript to extend PocketBase functionality?
It looks like backend APIs need to be written in JS and and then deployed as separate files. Not quite what I want, but I can appreciate why they went that route.
It'd be neat to have a project like this as a Rust crate, where you can write your own APIs in Rust and compile the whole thing to a single file.
not quite the same but try loco.rs, for me its great
But I wonder who the audience is... The website says "serve millions of customers from a tiny box". Who does this appeal to ?
Solo developers who have million of users, need very low latency, yet are happy with a just a SQLite database for their backend ?
And by the time they get a million user, they will probably not be hosting on a single "tiny box" anyway. At what point do you think : okay, I need PocketBase, but ten times faster ?
Definitely considered a bunch of alternatives. "SecondBase" was fun :)
Does it mean sqlite, so just 1 file which is the database?
> This file implements a VFS shim that allows an SQLite database to be appended onto the end of some other file, such as an executable.
(A quick search of their codebase reveals no results for "appendvfs", so I guess not. So single-file was a lie!)
Storage on disk is actually a couple of files of data bases, uploaded files, keys, config, ...
- Lang: Go vs Rust
- JS Runtime: goja (ES5 only) vs V8
Who the hell changes passwords on a demo site?