New SwiftUI like declarative syntax for Swift for servers

I've been using SwiftUI for a while and loving it. I've been wanting to program in swift on server side for so long. But all other server side swift solutions are same and makes no new ground.
For example most server side swift libraries(Kitura, Perfect etc) has nearly identical syntax to other server side scripting languages(node, python etc)

So I created this library for server side programming. Only the syntax is working its not handling any requests or sql connections right now but can be implemented if we can get enough people.

Everything is type safe and asynchronous. Swifty :slight_smile:

Example requests

Every request has its input(like post parameters or get params) and it can manipulate its environment with those inputs. My main goal is to write safe server side code with as less code as possible.

Example Enviroment

Example Code

I didn't made it to a new library to speed up the testing.

I need some thoughts this project. If you like it you can help me build it. So we can have our own server side syntax as Swift community!

Everything is type safe so I can make a tool that automatically prints all api documentation.

8 Likes

I'm really excited for more diverse SSS competition to arise, and I think this sort of thing is a great start! Is it built on NIO?

2 Likes

I've not built the server components yet. But it'll be built upon NIO

The syntax here is very compelling. Although I'm not a bug server-side Swift user at this point, I'd be quite interested to see how you grow with this project.

Let us know what we can do to support from afar!

1 Like

I've updated the library and made it little pretty :slightly_smiling_face:

And server side components are working with swift nio now! (not the prettiest implementation)
Only the sql stuff are not working.

Here is the repo:
swiftyserver/SwiftyServer
And the example code: Main.swift

1 Like

This really inspired me, but there are some details in the syntax I'm not crazy about. I decided to put together my own prototype. Here is a working example server (you can also see the code on GitHub here).

import Firn

let api = API()

api.configureAuthentication(for: User.self) { request in
    // An authorization configuration must return a user if the auth is valid.
    // If it returns nil, the API will return an unauthorized response.
    guard request.headers["Authorization"].contains("Bearer SECURE") else {
        return nil
    }
    return User(name: "Username")
}

try api.addRoutes {
    // Returns an empty 200 status
    GET("ok")

    // Returns a gone status
    GET("gone")
        .status(.gone)

    // Returns "Hello World!"
    GET("hello")
        .toText({ _ in "Hello World!"})

    // Returns whatever data is passed in
    POST("echo")

    Auth(User.self) {
        // Returns a message confirming authentication as long as it
        // includes the "Authorization: Bearer SECURE" header as defined
        // in the auth configuration above
        GET("restricted")
            .toText({ req in
                let user = try req.authorizedUser(User.self)
                return "Look at you, \(user.name), you're authorized!"
            })
    }

    Auth(User.self) {
        // Defines a group of endpoints all at "/tasks"
        Group("tasks") {
            // Return a json representation of a Task
            GET("new")
                .toObject({ _ in Task(name: "") })

            // Parses and returns an json representation of a Task
            POST()
                .toObject(Task.self)

            // Returns a Task with the id in the path e.g. 3 for "/tasks/3"
            GET(Var.int)
                .toObject({ Task(id: try $0.pathParams.int(at: 0), name: "Some task", isComplete: false) })

            // Parses and returns an json representation of a Task
            PUT(Var.int)
                .toObject(Task.self)
        }
    }
}
api.run()

The big differences I see from what you implemented:

  1. Eliminate a lot of the boilerplate repetition of MyEnvironment.
  2. Grouping endpoints
  3. Variable path components (capture strings and ints from the path automatically)
  4. Eliminate "response" method. All states of mapping will result in some sort of logical response so that boilerplate isn't necessary).
  5. Language differences in the methods on the endpoints
  6. Auth does not encourage forced unwrapping and instead throws an error

Mine is also implemented on Swift NIO (and also very rough right now). I got pretty far implementing my own DB functionality based on my existing SQL libraries, even to the point of it functioning to do things like Get("posts").list(Post.self) to automatically retrieve all of the posts from the DB, but decided to pull back for now and rethink the DB implementation.

I've also started to play around with adding the ability for it to automatically generate working Swift client code on this branch. It's going well but the major sticking point will be properly generating the input and output declarations when using custom types.

My big design goal is to create as fast and easy of a dev experience as possible. I'm still deciding if I want to try to bring in an existing DB implementation (like Vapors), use my existing one (SQL), or start a new one.

3 Likes

Looks lovely. I was just about to create a grouping method like this and I even solved authentication without any optional types or try catch methods. I’ll push my method tomorrow you can check it out!
I really want to see this becoming a new way of server side programming.