This is some of the cleanest, modern looking, beautiful C code I've seen in a while. I know it's not the kernel, and there's probably good reasons for lots of #ifdef conditionals, random underscored types, etc in bigger projects, but this is actually a great learning piece to teach folks the beauty of C.
I've also never seen tests written this way in C. Great work.
C was the first programming language I learned when I was still in middle/high school, raising the family PC out of the grave by installing free software - which I learned was mostly built in C. I never had many options for coursework in compsci until I was in college, where we did data structures and algorithms in C++, so I had a leg up as I'd already understood pointers. :-)
Happy to see C appreciated for what it is, a very clean and nice/simple language if you stay away from some of the nuts and bolts. Of course, the accessibility of the underlying nuts and bolts is one of the reasons for using C, so there's a balance.
Ok I hear this all the time. Are pointers really that hard for so many people to understand? I'm not trying to brag it took me I think like 15 minutes to grok them from learning about them the first time. I'm sure it took me longer to be proficient but I don't get this legendary difficulty aura that seems to surround their existance.
Also yes nice project.
Job app complete projected archived and abandoned in 3...2..1... :). I hope not.
The issue with pointers is that CS gets taught in a VERY bad way. The way it should be taught is starting with basic assembly on a microprocessor. This trains your brain to think of memory locations and data in memory.
Then when you start using pointers, it makes sense. If variable is a pointer, that means its a memory location. *variable is a way to get that data. Then arrays is just a wrapper around pointer arithmetic.
Whereas with CS, you learn about variables first, which is an abstraction on top of memory, and pointers don't make sense in this regard.
This is why any EE/ECE grads are much better developers than CS grads, because once you understand fundamentals
It's a rabbithole. Pointer to array of structures that have pointer fields. Array of pointers to structures etc. You pass them around and trip over the passing semantics, uninitialised pointers etc etc.
> Are pointers really that hard for so many people to understand?
Apparently they are; I believe it's the indirection that gets people.
Most learners aren't really taught basics properly - they learn that a variable "contains" a value, when instead they should learn that all values have a type, and some variables hold values.
> I'm not trying to brag it took me I think like 15 minutes to grok them from learning about them the first time.
I can't remember not knowing pointers, so I can't really tell you how long it took for it to click, but I do know that I had done a non-trivial amount of assembly before I used C, so maybe that helped/.
The worst thing with C pointers was for me that the asterisk is inexplicably used both to declare a pointer and a COMPLETELY different operation of dereferencing a pointer.
I still don't understand this decision. I think it should've been like int^ p = &i; ... or ... int i = *p;
Everything clicked ironically when I went even deeper and studied assembly language. Then following pointers to data vs just reading pointers becomes very clear and explicit.
The problem arises when you start to mix memory management with more complex structures.
It’s extremely easy to make mistakes, and you must be very careful about ownership and clean up. Those things are not strictly related to pointers, but in C, it’s inevitable to use pointers to handle them. That's why people say pointers are hard.
Pointers in and of themselves are not difficult to learn on their own, but when you're learning them alongside your first programming language, it's just adds to the difficulty I think.
I think a lot of noobs learning C struggle with pointers especially because there are no good error messages besides "segmentation fault" :D
I learned assembly language long before I learned C, so pointers took me about 2 seconds to understand. I suppose it may depend on previous experience.
> Are pointers really that hard for so many people to understand?
Yes. Especially pointer to pointer to ...
The big problem is that arrays are conflated with pointers because C doesn't do slices. If C had slices, people would naturally avoid pointers except in the cases where they were genuinely necessary. That would make teaching pointers vastly easier.
As a 16 year old who learned programming by hacking together bad ActionScript3 flash games, it took me way longer than 15 minutes. Once I got it I got it and there was no mystery from then on. But there definitely was a hurdle.
The legendary status is also enhanced by the absolute nightmare that pointers enable if used with indiscretion or high level proficiency - a triple pointer is a good example for me but there's many many more, and arguably worse, examples out there.
Same here about pointers. Perhaps it's cause I started life as an electronic engineer and understood memory addressing from the chip level but I, too, don't understand the struggle others seem to have.
You've done a couple of things right: very few dependencies, simple, easy to understand code. C gets hairy when you try to be clever.
I'm busy writing some of the most optimized-but-still-portable code that I've ever written and it is very interesting to see how even a slight difference in how you express something can cause a massive difference in execution speed (especially, obviously, in inner loops). Your code is clearly written from what your comfort zone with C is and I'm really impressed by the restraint on display. At the same time, some of the code feels a bit repetitive and would benefit from more universal mechanisms. But that would require more effort and I'm not even sure if that is productive. One part where I see this is in the argument parsing code as well as in the way you handle strings, it is all coded very explicitly, which substantially increases the chance of making a mistake.
Another limitation is that using AI to help you write the code means you don't actually understand what it does, and this in turn may expose you to side effects that you are not able to eliminate because you did not consider them while writing, it is as if someone else gave you that code and asked you to trust them they did not make any mistakes.
People, stop trying to be so serious and nitpick this project. This is a great example of an actual HN worthy share. Someone built a cool project and explored the possibilities with C. This is not something we need to analyze with "oh can it replace PHP" etc.
Good job OP. Now if you can add HTML templating, this may become a complete framework :)
The code is very readable and well organized. My only major critique is that there's very little error checking, e.g. there are many calls to snprintf and malloc without checking the result. There is also an unused loop here [1].
As an aside, I don't see any support for parallelization. That's fine for an initial implementation, but web servers do benefit from threading off requests. If you go that route (pun intended) you might consider using something like libuv [2].
Hi, I think this is great. I've really enjoyed working with Jetzig, which is sort of similar.
I also love the BSD C CGI Postgres stack. I'm just a CRUDmonkey with mostly python skills, so getting to explore low language and memory concepts is a lot of fun for me.
People will whine and moan about how this is not practical, but as embedded devices become more ubiquitous I think a clear value add may actually emerge.
I've been playing with the pico calc, and if I was building something as a "mobile app" for that I would much rather reach for C for my framework code.
Well I don't know about others here, but I think its cool. If you can make the setup super readable and get the performance of C then why not? Especially now when you can get claude to write a bunch of the framework for you. Add in whatever you need whenever you need it and you automatically have a platform independent web framework that's no bigger than what you need and likely decently performant.
Haha, I have used AI in some parts of it - mainly the JSON part because I could not wrap my head around it for the life of me. But I am proud that 90% is self written!
I think the old HN ethos that I loved, on full display here, won't survive intact in the AI era. It'll have to change from "It is cool to try making <neat tool> in <non obvious language>". Such a project is now a prompt away, and there's light-years of distance between a carefully hand crafted version and something that is posted aspirationally by an AI.
Every agent I know of or use will always say they built "Production ready, secure, fast package for X" if you ask them to build that, but they rarely actually will. It takes enormous time and effort to actually do that, and any first iteration of "production ready" is definitely aspirational until it actually hits the real world and survives. I'm speaking from experience, fwiw.
That's awesome. With macros, you can go far and most modern web frameworks use whatever complex tools their language allows (like metaprogramming in Rails).
Mad props for building this. It's hard and it's fun!
As to other comments in the thread about the "why": why not. For the love of the craft.
It's very dangerous to write a http parser from scratch in C. This can be very vulnerable without rigorous testing. To get a useful web framework for production in C, I think it's a better idea to start from libmicrohttpd, libevent_http, or even fastcgi, which are battle-tested.
I hear this comment warnings, and can easily see this myself being true. But, how could one actually make a reasonably safe http server in C from scratch?
That would honestly sound like an amazing book, just walking through all the ways it's horrible chapter by chapter, and how to structure the code instead, slowly. Like an accelerated history to create such a matured http library.
wow that’s a lot of HATE for a really well organized project with some great ideas. Killer job Ashton, you just built some skills they can’t take away from you.
A couple of notes: you'll want to use non-blocking I/O and an event loop to prevent one slow client from locking up the whole server. You should also check for partial read and write calls, so that if a client sends a couple bytes at a time, you can buffer up their full response and still be able to respond to it. A fixed size buffer for requests isn't ideal either since POST requests can easily blow through your 4096 byte buffer.
You might also want to look into using an AF_INET6 socket. You can still accept IPv4 connections, but you'll also gain IPv6 basically for free, and in 2025, you really should support IPv6.
I hope you don't feel discouraged by some comments questioning the meaningfulness of this. It's a cool project, and you obviously put some thought into it. Congrats!
> I hope you don't feel discouraged by some comments questioning the meaningfulness of this. It's a cool project, and you obviously put some thought into it. Congrats!
I feel like there is too much positive reinforcement for this project (see the comments about how clean the code is, this is how C should be written, etc).
This project is an exceptionally poor example of well-written C. I know the author states that AI was used for the JSON bits, but I rather doubt it based on how over-engineered the env file parsing is.
When C is written in this way it becomes harder to spot bugs.
"That's odd, but at least it's better than having the negative ones at the top."
You're being too kind, but I suppose your work requires diplomacy. When multiple top-level comments basically say "I can't believe the amount of hate in this thread", without adding any other thought, the thread start to feel like a simulacra of a discussion, an absurd comedy of nonsense, a deeply layered shaggy dog story.
Come on, people. Want to counterbalance negative comments? Find something positive and interesting to say. Yes, this will require actual effort, as with many things in life. You can do it!
Thank you for the feedback! I agree it does obfuscate the signature, possibly too much. I think it's okay as long as the user has the option to do both, with this made clear in the docs.
Also, yes I agree, RequestContext makes more sense!
Great work! Thank you! That's what I've been looking for for a long time.
Still probably I'm going to continue learning golang in most situations, because that's where the money is (i.e. job offers), but I will create a hobby project based on your framework.
--- EDIT ---
> 5 hours ago
Ohh it's fresh. I almost smell the freshly baked buns with my mind
However, it doesn't seem that the parser supports comments. I guess a "good first issue" for anyone interested in contributing would be extending the `skipWhitespace` function to detect `#` tokens and skip the rest of the line when present.
Would also need to handle edge cases like env vars having values containing `#` tokens inside (but these will be quoted, so it's probably not too tricky to handle.)
> Additionally, the .env file parser is quite clean.
I didn't find it clean; it's so over-engineered that you won't easily be able to spot bugs in it.
What you want is (assuming you have a string-trimming function):
while ((fgets (name, sizeof name, inputf)) {
if (!(value = strchr (name, '='))) { continue; }
*value++ = 0;
strtrim(name);
strtrim(value);
if (!*name) { continue; }
// Now store `name` and `value`
}
> I guess a "good first issue" for anyone interested in contributing would be extending the `skipWhitespace` function to detect `#` tokens and skip the rest of the line when present.
Or do it before processing:
// First statement of while loop
char *comment = strchr (name, '#');
if (comment) *comment = 0;
// Continue with processing `name`
The way it's done in the linked code raises a ton of red flags.
So what happens when the env value happens to actually have a # in it?
So you then need to implement escaping which can go from a very simple implementation to an actual lookahead parser
EDIT:
Actually I agree, this parser is already very overbuilt and should be able to handle comments. Generally an env parser is a few lines a best… you need to read a line, look for the first instance of the separator, generally no reason to build a full parser for that, env is an absurdly simple format if you don’t want features like comments.
Hell even an ini format parser is simple to implement in the same style.
Thanks for sharing; small is beautiful. A couple of points of feedback:
- check return value from malloc();
- consider using your own arena allocator (which gets a larger block of memory with a one-time call of malloc, then calls an in-process allocator that assigns part of that block);
- use a library prefix e.g. Lavandula_ before API functions like get() or runApp() to avoid name collisions.
- The JSON function is not spec-compliant; why not use an existing library? (I understand external dependecies may introduce unwanted bloat, but in this case, there are many compact and efficient options.)
> consider using your own arena allocator (which gets a larger block of memory with a one-time call of malloc, then calls an in-process allocator that assigns part of that block);
This is what malloc already does. Unless you intend to have multiple different arenas in your program, this is just unnecessary complexity.
I have considered porting a couple production apps from python to C; at this stage in their lifecycle they would benefit more from C's execution speed than from python's development speed.
Your work is a nice reference, it is neat to see someone else working in this space!
C is really, really ripe for tooling and modern libraries. There are a lot of great ones already that don’t resemble what I’ll call university C in the slightest (i.e. the C most of us remember writing; awful, bug filled, segfaulting)
I’ve been building out my C standard library replacement in earnest for a little while. If you like this framework, check it out.
That’s quite impressive, I love this c renaissance! I noticed your project requires c++ compiler as well, I didn’t study it to understand why? Do you plan to keep that requirement?
Arenas would be a better fit for a webserver. Allocations are basically free and everything gets deallocated all at once, so having one arena per active request and resetting it between requests can give very high performance.
Using alloca will result in stack overflows if you use more than a couple megabytes, so it isn't a very good idea.
I don't know who told you. But it's a lot slower than malloc, and requires you to do a bunch of bookkeeping, which is easy to mess up if you have multiple exits from your function.
OP, Turn on CodeQL workflows in GH, you'll thank me later! Even if your code is bug free, it makes it easier to find out when common mistakes are introduced later on. Especially since you're asking for help with features and others will be doing PRs.
Great project. I remember using mongoose a while back that's also written in C. Personally, the more library independent and self-sufficient it is, the more I'm likely to use it. Like, if you can even avoid using the stdlib! Even thought that sounds crazy (but a server in C is a bit crazy anyways?). The more standalone it is, the more transformable and embeddable it can be.
Real question, how did you learn how to code this well? I found your LinkedIn from your Github, and as someone who is just committing to becoming a SWE at 26, having learned a bit of Python and Matlab in college, and a bit of Java in high school, yet never fully grasped it and thus avoided it for as long as possible, I'm impressed by people who have this caliber of abilities at such a young age. Are there any tips or bits of advice you (or anyone else on HN for that matter) would give to someone who really wants to be the best they can possibly be at coding?
Hi, I have been coding on GitHub for about 2 years. There's no trick other than enjoying it and relentlessly programming for fun. You probably know this, but if you enjoy something, you'll likely be better at it than someone who doesn't! More concrete advice would be to become an expert on the fundamentals and then try to tackle large projects, things that you think you could definitely not do, but do them anyway.
I appreciate the response! Was there any structured curriculum you used for learning the fundamentals? As someone who isn’t in a CS program, I was wondering where a good place to start would be
> That's a great example of how to write C in 2025. Congrats and well done.
This project is an awful example of how to write C. No checking of return values, leaking memory with realloc, over-engineered parsing (what should be 8 lines is +200).
I can understand it as a learning project, and even if it wasn't, I can sorta understand that sometimes bugs creep in ("oops, forgot to use a tmp variable for realloc in one out of 10 places") but this is not what is happening: This is not how you write C!
The repo looks fantastic! I'd love to see a demo and didn't seen one readily available in the readme.
I had such a bad experience with GWT back in the Java days of my life that I've steered clear of any "server" language for web frameworks since. I'd love for that to change though. I definitely will be trying this out.
I like this, thanks for sharing. I recently did some work with a python web server using the basehttpserver and it was amazingly easy. Pythons even got built in tls support, would that be doable in your server? Its not that necessary with reverse proxies but its still nice for hobby projects.
really nicely written. inrespect this is maybe known / unneeded comment, but why bother with basic auth at all, especially when there is no TLS?
i understand other auth schemes are more complicated, and maybe theres no desire to pull in big libraries. just that if theres no TLS or proper auth, you can also just skip basic auth. its only use would be to trick someone who's not familiar (unlikely with such a repo but not impossible) into a false sense of security.
ofc, not really an issue with the code, and its an excellent base to look into how this stuff works and if you want since its pretty clean and easy to ready, expand upon it. well done! love ppl churning out good ol C projects. respect!
It's fairly common to use something like nginx as a forward proxy and do TLS there. IPv4 and NAT makes this essentially mandatory if you want to host multiple services due to eSNI. You wouldn't necessarily have protection inside the server network (which isn't great) but you at least get protection everywhere else.
> why bother with basic auth at all, especially when there is no TLS?
Maybe to have some "basic" auth for an embedded device web interface or something like that? I suppose it's better than nothing. I've devices which prompt for username and password with no TLS either.
Nice work! I like the little test framework you built. Have you considered making runTest a macro so that you can print the name of the test along with the test result?
I don't understand the example. Does it even compile?
It's been a long time since I've used C, so maybe it's using some syntax that I'm unaware of?
IE: What defines "home" that is referenced as an argument to the "appRoute" function, and then passed to the "get" function to set up the "/home" route? Is "home" defined in lavandula.h, or is this really pseudocode?
Hi, sorry maybe I should've added a comment for that.
The 'appRoute' is a macro that expands to a function signature.
The macro is: '#define appRoute(name) HttpResponse name(AppContext ctx)' and the parameter I passed as 'home' is expanded into the function name. The reason is because all controller function signatures are the same, so just writing 'appRoute' allows the developer to save time writing endpoints!
It is a tradeoff between readability and development speed. And one of the ideas behind the framework is succint and minimal code.
If I can guess, I would say `appRoute` is a macro that defines a struct called `home` with that handler being assigned to some field as a function pointer.
I think it makes lots of sense when adding e.g. a live view to some C daemon running on a single board computer. Obviously in these cases you're not generally on the public Internet and your clients are trusted.
Right now, it's just a framework for building backends. So yes, server-side applications. However, I have thought about implementing a templating engine for serving HTML files.
Edit: I am considering to delete the following paragraph as it seems that my hands were quicker than my brain :)
I'm sorry, but it's like scratching your left ear with your right hand. But for fun, yeah, there are worse things people do. Good luck and have fun. Now here's where most of us will probably be sarcastic, but it's certainly a good way to explore whatever others consider bullshit.
Edit: Pls read the following comment. I would hire him/her because I consider this as a waste of OP skills and he/she would be useful in many more projects.
TLDR; it was not a hate. I am sorry if it sounds so.
Use static analysis (Coverity, Coccinelle, sparse), enable KASAN/UBSAN, follow the SEI C Coding standard or MISRA C, and rely on the review process.
Many popular C projects do really well. Projects that you probably use.
Memory-safe languages eliminate vulnerability classes, but well-engineered C has proven viable for security-critical <insert whatever you want> infrastructure. The real question is whether the framework maintains that standard, not whether C is inherently unsuitable, thus the security concerns are legitimate but not absolute.
I think you are being a bit too dismissive, and your comment puts nothing concrete on the table.
You CAN write good code in any language. The issue is, as you say, that memory-safe languages eliminate entire vulnerability classes, vulnerability classes that are among the most trivially exploitable.
Can write safe code does not mean always writes safe code. A web server needs to be safe code, always.
This is some of the cleanest, modern looking, beautiful C code I've seen in a while. I know it's not the kernel, and there's probably good reasons for lots of #ifdef conditionals, random underscored types, etc in bigger projects, but this is actually a great learning piece to teach folks the beauty of C.
I've also never seen tests written this way in C. Great work.
C was the first programming language I learned when I was still in middle/high school, raising the family PC out of the grave by installing free software - which I learned was mostly built in C. I never had many options for coursework in compsci until I was in college, where we did data structures and algorithms in C++, so I had a leg up as I'd already understood pointers. :-)
Happy to see C appreciated for what it is, a very clean and nice/simple language if you stay away from some of the nuts and bolts. Of course, the accessibility of the underlying nuts and bolts is one of the reasons for using C, so there's a balance.
> I'd already understood pointers.
Ok I hear this all the time. Are pointers really that hard for so many people to understand? I'm not trying to brag it took me I think like 15 minutes to grok them from learning about them the first time. I'm sure it took me longer to be proficient but I don't get this legendary difficulty aura that seems to surround their existance.
Also yes nice project.
Job app complete projected archived and abandoned in 3...2..1... :). I hope not.
The issue with pointers is that CS gets taught in a VERY bad way. The way it should be taught is starting with basic assembly on a microprocessor. This trains your brain to think of memory locations and data in memory.
Then when you start using pointers, it makes sense. If variable is a pointer, that means its a memory location. *variable is a way to get that data. Then arrays is just a wrapper around pointer arithmetic.
Whereas with CS, you learn about variables first, which is an abstraction on top of memory, and pointers don't make sense in this regard.
This is why any EE/ECE grads are much better developers than CS grads, because once you understand fundamentals
10 replies →
It's a rabbithole. Pointer to array of structures that have pointer fields. Array of pointers to structures etc. You pass them around and trip over the passing semantics, uninitialised pointers etc etc.
8 replies →
> Are pointers really that hard for so many people to understand?
Apparently they are; I believe it's the indirection that gets people.
Most learners aren't really taught basics properly - they learn that a variable "contains" a value, when instead they should learn that all values have a type, and some variables hold values.
> I'm not trying to brag it took me I think like 15 minutes to grok them from learning about them the first time.
I can't remember not knowing pointers, so I can't really tell you how long it took for it to click, but I do know that I had done a non-trivial amount of assembly before I used C, so maybe that helped/.
10 replies →
The worst thing with C pointers was for me that the asterisk is inexplicably used both to declare a pointer and a COMPLETELY different operation of dereferencing a pointer.
I still don't understand this decision. I think it should've been like int^ p = &i; ... or ... int i = *p;
Everything clicked ironically when I went even deeper and studied assembly language. Then following pointers to data vs just reading pointers becomes very clear and explicit.
4 replies →
Understanding the concept is easy.
The problem arises when you start to mix memory management with more complex structures.
It’s extremely easy to make mistakes, and you must be very careful about ownership and clean up. Those things are not strictly related to pointers, but in C, it’s inevitable to use pointers to handle them. That's why people say pointers are hard.
1 reply →
> Are pointers really that hard for so many people to understand?
Yes, anyone who has taken algorithms and data structures class in C knows that some people just don't get it.
Also the way people teach it tends to be bad, before teaching pointers you need to teach Stack and Heap at a conceptual level.
"Are pointers really that hard for so many people to understand?"
The * vs & always gets me and not to mention if I ever have to deal with Pointer Math.
6 replies →
Pointers in and of themselves are not difficult to learn on their own, but when you're learning them alongside your first programming language, it's just adds to the difficulty I think.
I think a lot of noobs learning C struggle with pointers especially because there are no good error messages besides "segmentation fault" :D
The concept is straightforward. The syntax isn't. That's why cdecl.org exists.
1 reply →
I learned assembly language long before I learned C, so pointers took me about 2 seconds to understand. I suppose it may depend on previous experience.
> Are pointers really that hard for so many people to understand?
Yes. Especially pointer to pointer to ...
The big problem is that arrays are conflated with pointers because C doesn't do slices. If C had slices, people would naturally avoid pointers except in the cases where they were genuinely necessary. That would make teaching pointers vastly easier.
I understood them on a superficial level when first learning about them, but it only really clicked after having done assembly.
As a 16 year old who learned programming by hacking together bad ActionScript3 flash games, it took me way longer than 15 minutes. Once I got it I got it and there was no mystery from then on. But there definitely was a hurdle.
The legendary status is also enhanced by the absolute nightmare that pointers enable if used with indiscretion or high level proficiency - a triple pointer is a good example for me but there's many many more, and arguably worse, examples out there.
1 reply →
Same here about pointers. Perhaps it's cause I started life as an electronic engineer and understood memory addressing from the chip level but I, too, don't understand the struggle others seem to have.
1 reply →
Wow! That really means a lot because I always make a lot of effort to make sure my code is just that :)
Appreciate you saying that!
You've done a couple of things right: very few dependencies, simple, easy to understand code. C gets hairy when you try to be clever.
I'm busy writing some of the most optimized-but-still-portable code that I've ever written and it is very interesting to see how even a slight difference in how you express something can cause a massive difference in execution speed (especially, obviously, in inner loops). Your code is clearly written from what your comfort zone with C is and I'm really impressed by the restraint on display. At the same time, some of the code feels a bit repetitive and would benefit from more universal mechanisms. But that would require more effort and I'm not even sure if that is productive. One part where I see this is in the argument parsing code as well as in the way you handle strings, it is all coded very explicitly, which substantially increases the chance of making a mistake.
Another limitation is that using AI to help you write the code means you don't actually understand what it does, and this in turn may expose you to side effects that you are not able to eliminate because you did not consider them while writing, it is as if someone else gave you that code and asked you to trust them they did not make any mistakes.
People, stop trying to be so serious and nitpick this project. This is a great example of an actual HN worthy share. Someone built a cool project and explored the possibilities with C. This is not something we need to analyze with "oh can it replace PHP" etc.
Good job OP. Now if you can add HTML templating, this may become a complete framework :)
Thank you, I really appreciate you saying that!
Yes it's on the backlog and will be fun to implement :)
As someone learning C for fun, I agree. This project is awesome!
The code is very readable and well organized. My only major critique is that there's very little error checking, e.g. there are many calls to snprintf and malloc without checking the result. There is also an unused loop here [1].
As an aside, I don't see any support for parallelization. That's fine for an initial implementation, but web servers do benefit from threading off requests. If you go that route (pun intended) you might consider using something like libuv [2].
[1] https://github.com/ashtonjamesd/lavandula/blob/51d86a284dc7d...
[2] https://github.com/libuv/libuv
Thank you for the feedback, it is appreciated!
I did intend to implement parallelization as a later feature so it's good to bring it up.
Hi, I think this is great. I've really enjoyed working with Jetzig, which is sort of similar.
I also love the BSD C CGI Postgres stack. I'm just a CRUDmonkey with mostly python skills, so getting to explore low language and memory concepts is a lot of fun for me.
People will whine and moan about how this is not practical, but as embedded devices become more ubiquitous I think a clear value add may actually emerge.
I've been playing with the pico calc, and if I was building something as a "mobile app" for that I would much rather reach for C for my framework code.
Cheers, great work
Well I don't know about others here, but I think its cool. If you can make the setup super readable and get the performance of C then why not? Especially now when you can get claude to write a bunch of the framework for you. Add in whatever you need whenever you need it and you automatically have a platform independent web framework that's no bigger than what you need and likely decently performant.
Maintainer nightmare checklist:
- Web framework : inherently hard to maintain due to communication over evolving standards. Check.
- AI written code where nobody knows howwhatwhenwhy!? Check.
- Written in C. Check.
bwahahahaha!
edit: semi-joking. As I actually like the simplicity of pure C. But the combination of AI written,network-facing and C makes me shudder.
Haha, I have used AI in some parts of it - mainly the JSON part because I could not wrap my head around it for the life of me. But I am proud that 90% is self written!
3 replies →
I think the old HN ethos that I loved, on full display here, won't survive intact in the AI era. It'll have to change from "It is cool to try making <neat tool> in <non obvious language>". Such a project is now a prompt away, and there's light-years of distance between a carefully hand crafted version and something that is posted aspirationally by an AI.
Every agent I know of or use will always say they built "Production ready, secure, fast package for X" if you ask them to build that, but they rarely actually will. It takes enormous time and effort to actually do that, and any first iteration of "production ready" is definitely aspirational until it actually hits the real world and survives. I'm speaking from experience, fwiw.
That's awesome. With macros, you can go far and most modern web frameworks use whatever complex tools their language allows (like metaprogramming in Rails).
Mad props for building this. It's hard and it's fun!
As to other comments in the thread about the "why": why not. For the love of the craft.
Thank you so much! I appreciate it :) And yes, totally agree.
It's very dangerous to write a http parser from scratch in C. This can be very vulnerable without rigorous testing. To get a useful web framework for production in C, I think it's a better idea to start from libmicrohttpd, libevent_http, or even fastcgi, which are battle-tested.
I don't think anybody here is going to use this for production, but just in case you're tempted: don't.
I hear this comment warnings, and can easily see this myself being true. But, how could one actually make a reasonably safe http server in C from scratch?
That would honestly sound like an amazing book, just walking through all the ways it's horrible chapter by chapter, and how to structure the code instead, slowly. Like an accelerated history to create such a matured http library.
I like your idea for the book. I hope that Robert Nystrom writes it.
wow that’s a lot of HATE for a really well organized project with some great ideas. Killer job Ashton, you just built some skills they can’t take away from you.
Thank you, that means a lot! :)
Very nice!
A couple of notes: you'll want to use non-blocking I/O and an event loop to prevent one slow client from locking up the whole server. You should also check for partial read and write calls, so that if a client sends a couple bytes at a time, you can buffer up their full response and still be able to respond to it. A fixed size buffer for requests isn't ideal either since POST requests can easily blow through your 4096 byte buffer.
You might also want to look into using an AF_INET6 socket. You can still accept IPv4 connections, but you'll also gain IPv6 basically for free, and in 2025, you really should support IPv6.
I hope you don't feel discouraged by some comments questioning the meaningfulness of this. It's a cool project, and you obviously put some thought into it. Congrats!
In addition, OP clearly describes themselves as a "Fanatical C Developer", so that's enough justification in my book! :)
Doing what you love is fully justified in 2025.
No of course not, I understand where they are coming from in all honesty. Thank you that means a lot!
> I hope you don't feel discouraged by some comments questioning the meaningfulness of this. It's a cool project, and you obviously put some thought into it. Congrats!
I feel like there is too much positive reinforcement for this project (see the comments about how clean the code is, this is how C should be written, etc).
This project is an exceptionally poor example of well-written C. I know the author states that AI was used for the JSON bits, but I rather doubt it based on how over-engineered the env file parsing is.
When C is written in this way it becomes harder to spot bugs.
Offtopic (sorry) but this thread is such a good example of the contrarian dynamic that I can't resist!
The "contrarian dynamic" (https://hn.algolia.com/?dateRange=all&page=0&prefix=true&sor.... That's probably the clearest way of describing the difference between the kind of comments we want on this site vs. the kind we don't want.
"That's odd, but at least it's better than having the negative ones at the top."
You're being too kind, but I suppose your work requires diplomacy. When multiple top-level comments basically say "I can't believe the amount of hate in this thread", without adding any other thought, the thread start to feel like a simulacra of a discussion, an absurd comedy of nonsense, a deeply layered shaggy dog story.
Come on, people. Want to counterbalance negative comments? Find something positive and interesting to say. Yes, this will require actual effort, as with many things in life. You can do it!
1 reply →
Some unsolicited feedback:
I think the appRoute macro obfuscates the types and signatures, and introduces some unnecessary indirection. I would get rid of it.
Related, the AppContext type could be renamed RequestContext or ControllerContext or something as its App + HTTP Request + DB and not just the App.
Otherwise, I agree with other commenters that this is some of the cleanest C code I’ve seen in a while! Great effort!
Thank you for the feedback! I agree it does obfuscate the signature, possibly too much. I think it's okay as long as the user has the option to do both, with this made clear in the docs.
Also, yes I agree, RequestContext makes more sense!
Great work! Thank you! That's what I've been looking for for a long time.
Still probably I'm going to continue learning golang in most situations, because that's where the money is (i.e. job offers), but I will create a hobby project based on your framework.
--- EDIT ---
> 5 hours ago
Ohh it's fresh. I almost smell the freshly baked buns with my mind
That's amazing to hear and motivates me to solidify the framework further. I appreciate you showing interest! :)
I'd love to hear about your project when you get round to it.
The README gets straight to the point and I really like that.
Additionally, the .env file parser is quite clean.
https://github.com/ashtonjamesd/lavandula/blob/main/src/dote...
However, it doesn't seem that the parser supports comments. I guess a "good first issue" for anyone interested in contributing would be extending the `skipWhitespace` function to detect `#` tokens and skip the rest of the line when present.
Would also need to handle edge cases like env vars having values containing `#` tokens inside (but these will be quoted, so it's probably not too tricky to handle.)
> Additionally, the .env file parser is quite clean.
I didn't find it clean; it's so over-engineered that you won't easily be able to spot bugs in it.
What you want is (assuming you have a string-trimming function):
> I guess a "good first issue" for anyone interested in contributing would be extending the `skipWhitespace` function to detect `#` tokens and skip the rest of the line when present.
Or do it before processing:
The way it's done in the linked code raises a ton of red flags.
So what happens when the env value happens to actually have a # in it?
So you then need to implement escaping which can go from a very simple implementation to an actual lookahead parser
EDIT:
Actually I agree, this parser is already very overbuilt and should be able to handle comments. Generally an env parser is a few lines a best… you need to read a line, look for the first instance of the separator, generally no reason to build a full parser for that, env is an absurdly simple format if you don’t want features like comments.
Hell even an ini format parser is simple to implement in the same style.
Why is there an env file parser at all?
Thanks for sharing; small is beautiful. A couple of points of feedback:
- check return value from malloc();
- consider using your own arena allocator (which gets a larger block of memory with a one-time call of malloc, then calls an in-process allocator that assigns part of that block);
- use a library prefix e.g. Lavandula_ before API functions like get() or runApp() to avoid name collisions.
- The JSON function is not spec-compliant; why not use an existing library? (I understand external dependecies may introduce unwanted bloat, but in this case, there are many compact and efficient options.)
> consider using your own arena allocator (which gets a larger block of memory with a one-time call of malloc, then calls an in-process allocator that assigns part of that block);
This is what malloc already does. Unless you intend to have multiple different arenas in your program, this is just unnecessary complexity.
I have considered porting a couple production apps from python to C; at this stage in their lifecycle they would benefit more from C's execution speed than from python's development speed.
Your work is a nice reference, it is neat to see someone else working in this space!
Would Go be a happy middle ground?
The translation should be much faster while giving a lot of the efficiency benefits of C (but by no means all).
or rust/zig as well.
C is really, really ripe for tooling and modern libraries. There are a lot of great ones already that don’t resemble what I’ll call university C in the slightest (i.e. the C most of us remember writing; awful, bug filled, segfaulting)
I’ve been building out my C standard library replacement in earnest for a little while. If you like this framework, check it out.
https://github.com/tspader/sp
That’s quite impressive, I love this c renaissance! I noticed your project requires c++ compiler as well, I didn’t study it to understand why? Do you plan to keep that requirement?
If you're going to use local allocation of short lived buffers then don't use malloc but use alloca. That's much cleaner.
http.c around line 398, that looks wrong.
Arenas would be a better fit for a webserver. Allocations are basically free and everything gets deallocated all at once, so having one arena per active request and resetting it between requests can give very high performance.
Using alloca will result in stack overflows if you use more than a couple megabytes, so it isn't a very good idea.
Those buffers are tiny and they won't result in stack overflows because the whole thing is single threaded, it is beginner level code.
I've been told that modern compilers really don't like alloca, is that wrong?
I don't know who told you. But it's a lot slower than malloc, and requires you to do a bunch of bookkeeping, which is easy to mess up if you have multiple exits from your function.
5 replies →
I'm wanting to do the same thing. I've also already written a language (in C) to generate HTML (a template language), so these two go hand-in-hand!
OP, Turn on CodeQL workflows in GH, you'll thank me later! Even if your code is bug free, it makes it easier to find out when common mistakes are introduced later on. Especially since you're asking for help with features and others will be doing PRs.
Great project. I remember using mongoose a while back that's also written in C. Personally, the more library independent and self-sufficient it is, the more I'm likely to use it. Like, if you can even avoid using the stdlib! Even thought that sounds crazy (but a server in C is a bit crazy anyways?). The more standalone it is, the more transformable and embeddable it can be.
There is a heap overflow in the http parser. Should I spoil it or let people find it on their own?
Heres a link to the the problem I found: https://alew.is/lava.html
This sounds essentially like Heartbleed.
Btw, you have a syntax error on line 13, the style tag isn't closed. Otherwise clean webpage.
Real question, how did you learn how to code this well? I found your LinkedIn from your Github, and as someone who is just committing to becoming a SWE at 26, having learned a bit of Python and Matlab in college, and a bit of Java in high school, yet never fully grasped it and thus avoided it for as long as possible, I'm impressed by people who have this caliber of abilities at such a young age. Are there any tips or bits of advice you (or anyone else on HN for that matter) would give to someone who really wants to be the best they can possibly be at coding?
Hi, I have been coding on GitHub for about 2 years. There's no trick other than enjoying it and relentlessly programming for fun. You probably know this, but if you enjoy something, you'll likely be better at it than someone who doesn't! More concrete advice would be to become an expert on the fundamentals and then try to tackle large projects, things that you think you could definitely not do, but do them anyway.
I appreciate the response! Was there any structured curriculum you used for learning the fundamentals? As someone who isn’t in a CS program, I was wondering where a good place to start would be
1 reply →
This is very cool. I may take the same concepts you have and do this in rust and zig for fun and learning.
Yeah, I know those languages have a the frameworks but nothing really beats understanding something like doing it ground up on your own.
I am convinced that well written C code is more aesthetically pleasing than well written code in other languages. Great job and thanks for sharing.
That's a great example of how to write C in 2025. Congrats and well done.
> That's a great example of how to write C in 2025. Congrats and well done.
This project is an awful example of how to write C. No checking of return values, leaking memory with realloc, over-engineered parsing (what should be 8 lines is +200).
I can understand it as a learning project, and even if it wasn't, I can sorta understand that sometimes bugs creep in ("oops, forgot to use a tmp variable for realloc in one out of 10 places") but this is not what is happening: This is not how you write C!
I created something similar few months back, just to learn. Integrated with sqlite.
It is fun to try but I realised there is a huge Mt. Everest to climb to make it anything close to fit for use.
https://github.com/fullobug/gttp
The repo looks fantastic! I'd love to see a demo and didn't seen one readily available in the readme.
I had such a bad experience with GWT back in the Java days of my life that I've steered clear of any "server" language for web frameworks since. I'd love for that to change though. I definitely will be trying this out.
I like this, thanks for sharing. I recently did some work with a python web server using the basehttpserver and it was amazingly easy. Pythons even got built in tls support, would that be doable in your server? Its not that necessary with reverse proxies but its still nice for hobby projects.
Yes, I'm sure that is something I can add to it.
I will add it to the backlog of things to do :)
really nicely written. inrespect this is maybe known / unneeded comment, but why bother with basic auth at all, especially when there is no TLS?
i understand other auth schemes are more complicated, and maybe theres no desire to pull in big libraries. just that if theres no TLS or proper auth, you can also just skip basic auth. its only use would be to trick someone who's not familiar (unlikely with such a repo but not impossible) into a false sense of security.
ofc, not really an issue with the code, and its an excellent base to look into how this stuff works and if you want since its pretty clean and easy to ready, expand upon it. well done! love ppl churning out good ol C projects. respect!
It's fairly common to use something like nginx as a forward proxy and do TLS there. IPv4 and NAT makes this essentially mandatory if you want to host multiple services due to eSNI. You wouldn't necessarily have protection inside the server network (which isn't great) but you at least get protection everywhere else.
> why bother with basic auth at all, especially when there is no TLS?
Maybe to have some "basic" auth for an embedded device web interface or something like that? I suppose it's better than nothing. I've devices which prompt for username and password with no TLS either.
Basic auth can keep the crawlers out, for one thing.
Nice work! I like the little test framework you built. Have you considered making runTest a macro so that you can print the name of the test along with the test result?
That's a very good idea actually and I had wanted to do that but it didn't click that you could do that with a macro!
Thank you, I'll will implement that :)
For the ultimate in readable test reports, you can prettify the test name by:
* dropping the prefix "test_" * substituting the "_" characters in the function for whitespace * uppercasing the first letter of each word.
So `test_tokenize_simple_model` becomes "Tokenize Simple Model".
I often forget how similar to Golang C looks and feels
Maybe more chronologically correct to say "how very similar to C that golang looks"
Choosing names like `App`, `ok` or `get` in a language without namespaces is a bold choice.
Very courageous. I would have fatigued in the middle, probably using ruby or python instead.
Curious, why did you decide to go with your own test helpers rather than using something like check?
Does it do HTTPS? I'd be interested to try it on ESP32 but it has to support HTTPS.
Couldn't believe my eyes, this is the cleanest C code I've ever seen!!
I like it. I was looking for something like this and I will take a look into it.
How compatible is this with embedded devices? How much does this depend on OS APIs?
Nice.
Is if a one thread per request model?
Does it support async await type of architecture?
awesome!
hope hw vendors will adopt it so their management web pages are less ass than they actually are currently.
Uhh... This is an example why C is so bad for network-facing stuff:
https://github.com/ashtonjamesd/lavandula/blob/2dbefe6da16bf... - is it intended?
https://github.com/ashtonjamesd/lavandula/blob/2dbefe6da16bf... - pain....
I get the first one but what is the issue with the second one? It looks like a fairly standard dynamic array with separate size and capacity.
Missing null pointer check on return, leaks memory because it overwrites the original pointer.
3 replies →
No null pointer check, and the fact that you have to manually rewrite it every time.
Great work, thanks for this!
I don't understand the example. Does it even compile?
It's been a long time since I've used C, so maybe it's using some syntax that I'm unaware of?
IE: What defines "home" that is referenced as an argument to the "appRoute" function, and then passed to the "get" function to set up the "/home" route? Is "home" defined in lavandula.h, or is this really pseudocode?
Hi, sorry maybe I should've added a comment for that.
The 'appRoute' is a macro that expands to a function signature.
The macro is: '#define appRoute(name) HttpResponse name(AppContext ctx)' and the parameter I passed as 'home' is expanded into the function name. The reason is because all controller function signatures are the same, so just writing 'appRoute' allows the developer to save time writing endpoints!
It is a tradeoff between readability and development speed. And one of the ideas behind the framework is succint and minimal code.
Thanks for explaining. I was also a bit confused at a first where the home variable being passed into get() was coming from.
So it creates a function called "home", and that is what you pass to get?
Makes sense, thanks!
"appRoute(home)" is a macro that expands to a function called "home":
If I can guess, I would say `appRoute` is a macro that defines a struct called `home` with that handler being assigned to some field as a function pointer.
It's a macro:
github is giving me 503, the project is too good for mS
Thanks for sharing, this looks amazing
which version of C does this conform to?
[dead]
[dead]
[dead]
[flagged]
C finds a way.
u should post this to people that write web with javascript
Take a look at https://www.reddit.com/r/programming/comments/225ovy/okws_ok...
This was years ago (20 years ago?)
[flagged]
A computer program written in a programming language.
Cobol and C are very different in terms of modern relevance.
a saas in prolog...
Don’t forget Fortran… /s
[flagged]
[flagged]
Haha, the example could be better. All of the other things combined, I would say it could be called a framework.
There are some more examples in doc/
For fun or why?
For fun! And because I wanted to create a framework that makes coding in C feel like a high level language (mainly for fun though).
Science isn't about why, it's about why not.
I think it makes lots of sense when adding e.g. a live view to some C daemon running on a single board computer. Obviously in these cases you're not generally on the public Internet and your clients are trusted.
I'm stupid — is this to create web apps that run on the server? More or less replacing PHP or whatever?
Right now, it's just a framework for building backends. So yes, server-side applications. However, I have thought about implementing a templating engine for serving HTML files.
This can be super useful for IoT or embedded devices with web interfaces but restricted resources.
Edit: I am considering to delete the following paragraph as it seems that my hands were quicker than my brain :)
I'm sorry, but it's like scratching your left ear with your right hand. But for fun, yeah, there are worse things people do. Good luck and have fun. Now here's where most of us will probably be sarcastic, but it's certainly a good way to explore whatever others consider bullshit.
Edit: Pls read the following comment. I would hire him/her because I consider this as a waste of OP skills and he/she would be useful in many more projects.
TLDR; it was not a hate. I am sorry if it sounds so.
19 replies →
[flagged]
Use static analysis (Coverity, Coccinelle, sparse), enable KASAN/UBSAN, follow the SEI C Coding standard or MISRA C, and rely on the review process.
Many popular C projects do really well. Projects that you probably use.
Memory-safe languages eliminate vulnerability classes, but well-engineered C has proven viable for security-critical <insert whatever you want> infrastructure. The real question is whether the framework maintains that standard, not whether C is inherently unsuitable, thus the security concerns are legitimate but not absolute.
I think you are being a bit too dismissive, and your comment puts nothing concrete on the table.
You CAN write good code in any language. The issue is, as you say, that memory-safe languages eliminate entire vulnerability classes, vulnerability classes that are among the most trivially exploitable.
Can write safe code does not mean always writes safe code. A web server needs to be safe code, always.
11 replies →