GraphQL Forum
Important
|
DO NOT even think about using this in production, lest your sanity be destroyed and credentials lost! |
Loosely following the awesome book Zero to Production In Rust, minus the vigorous test cases and battle tested framework.
TL;DR
- Summarize backend in one line?
-
Good performance without even trying, massive pain keeping SQL and code in sync, and huge trouble working around corners of API.
- Summarize frontend in one line?
-
Surprisingly pleasant to write, but documentation is scarce, and code size is large. WASM is probably not ready to be the "main site" yet.
- Comment the code quality?
-
It would be an excellent playground to practice exploits!
- How was the experience?
-
Extremely determined. Damn, full-stack is hard.
- Will I do this again?
-
No, not the full stack. If I want to build anything for real, I would go for something like Preact (for frontend) + Hasura (for GQL and data) + Ory Kratos (for auth). For other backend services that don’t involve dealing with data store, I would still consider Rust Axum, tower stack, and
async-graphql
to be good. - How is the code quality?
-
TheDailyWTF kind of quality.
Building and running
Make sure you have trunk installed.
Go to gqlforum-sycamore/
, then:
$ trunk build --release
Go to gqlforum-backend/
, then:
$ cargo run --release
Go to http://localhost:3000 for the main page. Go to http://localhost:3000/graphql for GraphQL playground.
Admin account is admin
, password is admin
.
The configs in configuration.toml
should be apparent.
Frontend Functionality
-
✓ Login page
-
✓ Logout link
-
✓ List topics
-
✓ Topic by id
-
✓ Create topic
-
✓ Create post
-
❏ Delete topic
-
❏ Delete post
Authentication
-
✓ Server side session management
-
✓ Signed secure cookie on client side, hashed secret on server side
-
-
✓ Password authentication with Argon2
-
✓ Login via username/password
-
✓ Registration (GraphQL only)
-
✓ Change password
Authorization
-
✓ Only admins/author can see deleted topics and posts
-
✓ Regular users can see their own posts/topics and any public posts/topics
-
✓ Regular users can only delete their own posts
-
✓ Admin can delete every post
Anti-features
-
✓ No HTTPS. Therefore, credentials are sent in clear text
-
Admittedly, this is easy to fix.
-
-
✓ Frontend crashes all the time. Try refreshing
-
✓ Horrendous UI design
-
✓ Despite the backend can paginate, you can only see the latest 10 topics on index page
-
✓ Despite the backend has this API, you cannot see user profiles at frontend
-
✓ Despite the backend has this API, you cannot register at frontend
-
✓ Despite the backend has this API, you cannot delete posts or topics at frontend
-
✓ Terrible error messages
-
✓
panic!
,unwrap
, andexpect
everywhere -
✓ Error handling scattered across front end, backend, on different layers of
Result
/Option
, etc. -
✓ Barely any defenses
-
✓ No checks what so ever in user input, but injection is guarded against
-
✓ Spaghetti code scattered around like crazy
-
✓ No documentation, no tests, no examples
-
✓ Monolith repo
-
✓ A test page that does no good except to verify my graphql implementation
-
✓ Stale sessions are not cleaned up regularly
-
✓ Random crashes if redirection goes too quickly
Design Choices
N+1
N+1 is not purposefully avoided. Joins are used to ensure correctness and access control, but not for performance (yet). See: https://www.sqlite.org/np1queryprob.html.
Experience Report
Warning
|
DO NOT IMPLEMENT PASSWORD AUTHENTICATION AND SESSIONS YOURSELF! |
The Good
-
Great performance without even trying
-
While I don’t have much web experience, the backend feels exceptionally fast
-
With
--release
, that is -
12MB memory use? Yeah, pretty good.
-
-
Axum comes with a great collection of middleware
-
async-graphql
object definition is relatively easy to use… once I got the basics-
I will continue to use it if I need to write a service to do something instead of to retrieve something. The latter is better done with existing solution, like Hasura
-
-
The compiler is very good at catching mistakes, if I am actually using types properly
-
Trunk sets up WASM output nicely
The Bad
- General
-
-
I have to keep the frontend/backend router in sync, manually.
-
For every route the SPA uses, I need the backend to serve the
index.html
-
-
Cargo workspace does not work well with mixed targets
-
There are these… "Context" paradigm which does something like
get_context::<Type>()
which I don’t like, because they destroy the point of having a statically type-checked language. And they are everywhere.
-
- Backend
-
-
Really, we are manually doing monad stack here by using all those
Context<'_'>
,Extension
,Layer
, … except without the nicedo
syntax Haskell provides -
async-graphql
doesn’t work very well with Axum middleware-
Cannot use
CookieJar
because we cannot return extra arguments-
Ended up rolling my own implementation to sign cookies
-
-
Repetition in binding middleware (in Axum and
async-graphql
)
-
-
sqlx
generics are extremely hard to type check, but I managed to use some anyways -
sqlx
macros do not work well with SQLite, because it type checks SQLite bytecode at compile time. This has some bugs, and is an extremely slow process
-
- Frontend
-
-
There aren’t any Rust GraphQL clients that work under WASM, so I rolled a simple one in a single file.
-
Trunk’s proxy doesn’t work. It just keeps redirecting until the browser refuses to continue.
-
Took me an enormous amount of time to figure out how to do async in WASM
-
Sycamore doesn’t have very good docs. I hacked around with terrible looking code.
-
Sycamore macros don’t work well with formatting
-
Sycamore’s routing seems a bit limited
-
Cannot figure out how to set status code for Sycamore
-
Fight the borrow checker with loads of
.as_ref()
and.clone()
-
Wasm is quite large, compared to JS libraries. I have practically all optimization turned to max in this project, and the size is still 327kB/129kB(gzipped). Also, it grows fast.
-
Conclusion
It started with me trying to make a small, monolith, self-contained forum that can run on extremely resource-limited machines. Then my sanity drained as I went along, and I cut features over and over, until I decide "this is going to be a technological study".
No, I will not continue developing this pile of diamonds. If I really want one in production, I am going for something else.
I do, however, think that this "full-stack" project contains some insights on the current ecosystem of Rust in web development, and some snippets that might be helpful to someone else.
Just don’t deploy it in production.