Also, creating a toy one for a simple language is a fun educational exercise. We shouldn’t deter people from having a go at that by making them think “compilers are hard”.
For many simple languages (e.g. a language doesn’t need to have strings, multiple numeric types, operator precedence, or even have expressions containing multiple operators), writing a one pass compiler that always spills back operation results to memory isn’t too hard. Its assembly output can fairly easily be matched to the source, and it will provide a decent speed-up compared to a naive interpreter.
From there, the first few steps (peephole optimizations to remove store-load pairs, then a slightly more advanced register allocator) aren’t too difficult, either, and bring nice speed ups.
‘Real’ compilers are ‘just’ thousands more of such steps, each either extending the language or improving code generation, up from there. And yes, things do become very hairy somewhere along that route.