{"id":25526236,"url":"https://github.com/exbotanical/ys","last_synced_at":"2025-04-11T04:51:27.793Z","repository":{"id":106027661,"uuid":"561100627","full_name":"exbotanical/ys","owner":"exbotanical","description":"The Modern Web Application Framework for C","archived":false,"fork":false,"pushed_at":"2023-10-30T04:49:04.000Z","size":1058,"stargazers_count":10,"open_issues_count":3,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-25T02:51:45.789Z","etag":null,"topics":["api-framework","server-framework"],"latest_commit_sha":null,"homepage":"https://exbotanical.github.io/ys","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/exbotanical.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2022-11-03T00:18:15.000Z","updated_at":"2024-12-09T00:47:38.000Z","dependencies_parsed_at":null,"dependency_job_id":"0f094cda-a27c-421a-bf1f-d5db485fb49e","html_url":"https://github.com/exbotanical/ys","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/exbotanical%2Fys","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/exbotanical%2Fys/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/exbotanical%2Fys/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/exbotanical%2Fys/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/exbotanical","download_url":"https://codeload.github.com/exbotanical/ys/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248345289,"owners_count":21088243,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api-framework","server-framework"],"created_at":"2025-02-19T21:17:03.028Z","updated_at":"2025-04-11T04:51:27.771Z","avatar_url":"https://github.com/exbotanical.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Ys\n\nYs is a modern, minimal web application framework for the C programming language. With Ys, you can bootstrap functional APIs with minimal dev effort using a rich feature-set of high-level utilities.\n\n[Read the docs](https://exbotanical.github.io/ys)\n\n## Code Examples\nWith Ys you can do something like this\n\n```c\n#include \"libys.h\"\n// ...\n\nys_response *handler(ys_request *req, ys_response *res) {\n  ys_set_header(res, \"Content-Type\", \"text/plain\");\n\n  ys_cookie *c = ys_cookie_init(COOKIE_ID, sid);\n  ys_cookie_set_expires(c, ys_n_minutes_from_now(10));\n  ys_set_cookie(res, c);\n\n  ys_set_body(res, \"Hello World!\");\n  ys_set_status(res, YS_STATUS_OK);\n\n  return res;\n}\n\nint main() {\n  ys_router_attr *attr = ys_router_attr_init();\n  ys_use_cors(attr, ys_cors_allow_all());\n  ys_router *router = ys_router_init(attr);\n\n  ys_router_register(router, \"/\", handler, YS_METHOD_GET);\n\n  ys_server *server = ys_server_init(router, PORT);\n  ys_server_start(server);\n\n  return EXIT_SUCCESS;\n}\n```\n\nAuth with Cookies:\n\n```c\n#include \u003cpthread.h\u003e\n#include \u003cstdint.h\u003e\n#include \u003cstdio.h\u003e\n#include \u003cstdlib.h\u003e\n#include \u003cstring.h\u003e\n#include \u003cuuid/uuid.h\u003e\n\n#include \"deps/jsob/jsob.h\"\n#include \"libhash/libhash.h\"\n#include \"libys.h\"\n\n#define SESSION_TIMEOUT_MINUTES 10\n\nstatic pthread_mutex_t session_mutex = PTHREAD_MUTEX_INITIALIZER;\nstatic pthread_mutex_t user_mutex = PTHREAD_MUTEX_INITIALIZER;\n\nhash_table *session_store;\nhash_table *user_store;\n\nchar *COOKIE_ID = \"DemoCookie\";\n\nconst int PASSWORD_HASH_KEY = 7;\n\ntypedef struct {\n  char *username;\n  char *password;\n} user;\n\nbool eq(const char *s1, const char *s2) { return strcmp(s1, s2) == 0; }\n\nchar *create_sid(void) {\n  char uuid[UUID_STR_LEN];\n\n  uuid_t bin_uuid;\n  uuid_generate_random(bin_uuid);\n  uuid_unparse(bin_uuid, uuid);\n\n  return strdup(uuid);\n}\n\nuser *create_user(char *username, char *password) {\n  user *u = malloc(sizeof(user));\n  u-\u003eusername = username;\n  u-\u003epassword = password;\n\n  return u;\n}\n\nbool user_exists(char *username) {\n  pthread_mutex_lock(\u0026user_mutex);\n  bool ret = ht_search(user_store, username) != NULL;\n  pthread_mutex_unlock(\u0026user_mutex);\n\n  return ret;\n}\n\n// This is for the demo ONLY! Do NOT use this for actual auth - it is NOT\n// secure!!!\nchar *hash_password(char *pw) {\n  char ret[sizeof(pw) + 1];\n\n  int i = 0;\n  for (; i \u003c strlen(pw); i++) {\n    ret[i] = (pw[i] ^ PASSWORD_HASH_KEY);\n  }\n\n  ret[i] = '\\0';\n\n  return strdup(ret);\n}\n\nys_response *index_handler(ys_request *req, ys_response *res) {\n  ys_set_status(res, YS_STATUS_OK);\n  ys_set_header(res, \"Content-Type\", YS_MIME_TYPE_HTML);\n  ys_set_body(res, ys_from_file(\"./index.html\"));\n\n  return res;\n}\n\nys_response *css_handler(ys_request *req, ys_response *res) {\n  ys_set_status(res, YS_STATUS_OK);\n  ys_set_header(res, \"Content-Type\", YS_MIME_TYPE_CSS);\n  ys_set_body(res, ys_from_file(\"./style.css\"));\n\n  return res;\n}\n\nys_response *data_handler(ys_request *req, ys_response *res) {\n  ys_cookie *c = ys_get_cookie(req, COOKIE_ID);\n  char *sid = ys_cookie_get_value(c);\n\n  pthread_mutex_lock(\u0026session_mutex);\n  char *username = ht_get(session_store, sid);\n  pthread_mutex_unlock(\u0026session_mutex);\n\n  ys_set_body(res, \"{ \\\"data\\\": \\\"Hello, %s!\\\" }\", username);\n  ys_set_header(res, \"Content-Type\", YS_MIME_TYPE_JSON);\n  ys_set_status(res, YS_STATUS_OK);\n\n  return res;\n}\n\nys_response *register_handler(ys_request *req, ys_response *res) {\n  char *username = jsob_getstr(ys_req_get_body(req), \"username\");\n  if (!username) {\n    ys_set_status(res, YS_STATUS_BAD_REQUEST);\n    ys_set_body(res, \"Must provide a username\");\n    return res;\n  }\n\n  char *password = jsob_getstr(ys_req_get_body(req), \"password\");\n  if (!password) {\n    ys_set_status(res, YS_STATUS_BAD_REQUEST);\n    ys_set_body(res, \"Must provide a password\");\n    return res;\n  }\n\n  if (user_exists(username)) {\n    ys_set_status(res, YS_STATUS_BAD_REQUEST);\n    ys_set_body(res, \"Username %s exists\", username);\n    return res;\n  }\n\n  user *u = create_user(username, hash_password(password));\n\n  pthread_mutex_lock(\u0026user_mutex);\n  ht_insert(user_store, username, u);\n  pthread_mutex_unlock(\u0026user_mutex);\n\n  char *sid = create_sid();\n\n  pthread_mutex_lock(\u0026session_mutex);\n  ht_insert(session_store, sid, username);\n  pthread_mutex_unlock(\u0026session_mutex);\n\n  ys_cookie *c = ys_cookie_init(COOKIE_ID, sid);\n  ys_cookie_set_expires(c, ys_n_minutes_from_now(SESSION_TIMEOUT_MINUTES));\n\n  ys_set_cookie(res, c);\n\n  ys_set_status(res, YS_STATUS_CREATED);\n  return res;\n}\n\nys_response *login_handler(ys_request *req, ys_response *res) {\n  char *username = jsob_getstr(ys_req_get_body(req), \"username\");\n  if (!username) {\n    ys_set_status(res, YS_STATUS_BAD_REQUEST);\n    ys_set_body(res, \"Must provide a username\");\n    return res;\n  }\n\n  char *password = jsob_getstr(ys_req_get_body(req), \"password\");\n  if (!password) {\n    ys_set_status(res, YS_STATUS_BAD_REQUEST);\n    ys_set_body(res, \"Must provide a password\");\n    return res;\n  }\n\n  if (!user_exists(username)) {\n    ys_set_status(res, YS_STATUS_BAD_REQUEST);\n    ys_set_body(res, \"invalid credentials\");\n\n    return res;\n  }\n\n  pthread_mutex_lock(\u0026user_mutex);\n  user *u = (char *)ht_get(user_store, username);\n  pthread_mutex_unlock(\u0026user_mutex);\n\n  if (!u || !s_equals(hash_password(u-\u003epassword), password)) {\n    ys_set_status(res, YS_STATUS_BAD_REQUEST);\n    ys_set_body(res, \"invalid credentials\");\n\n    return res;\n  }\n\n  char *session_id = create_sid();\n\n  pthread_mutex_lock(\u0026session_mutex);\n  ht_insert(session_store, session_id, username);\n  pthread_mutex_unlock(\u0026session_mutex);\n\n  ys_cookie *c = ys_cookie_init(COOKIE_ID, session_id);\n  ys_cookie_set_expires(c, ys_n_minutes_from_now(SESSION_TIMEOUT_MINUTES));\n  ys_cookie_set_path(c, \"/\");\n  ys_set_cookie(res, c);\n\n  ys_set_status(res, YS_STATUS_OK);\n\n  return res;\n}\n\nys_response *logout_handler(ys_request *req, ys_response *res) {\n  ys_cookie *c = ys_get_cookie(req, COOKIE_ID);\n  if (!c) {\n    ys_set_status(res, YS_STATUS_UNAUTHORIZED);\n    ys_set_done(res);\n\n    return res;\n  }\n\n  char *sid = ys_cookie_get_value(c);\n  if (!sid) {\n    ys_set_status(res, YS_STATUS_UNAUTHORIZED);\n    ys_set_done(res);\n\n    return res;\n  }\n\n  pthread_mutex_lock(\u0026session_mutex);\n  ht_delete(session_store, sid);\n  pthread_mutex_unlock(\u0026session_mutex);\n\n  ys_cookie_set_max_age(c, -1);\n  ys_set_cookie(res, c);\n\n  return res;\n}\n\nys_response *auth_middleware(ys_request *req, ys_response *res) {\n  ys_set_header(res, \"X-Authorized-By\", \"TheDemoApp\");\n\n  ys_cookie *c = ys_get_cookie(req, COOKIE_ID);\n  if (!c) {\n    ys_set_status(res, YS_STATUS_UNAUTHORIZED);\n    ys_set_done(res);\n\n    return res;\n  }\n\n  char *sid = ys_cookie_get_value(c);\n\n  pthread_mutex_lock(\u0026session_mutex);\n  char *username = ht_get(session_store, sid);\n  pthread_mutex_unlock(\u0026session_mutex);\n\n  if (!username || !user_exists(username)) {\n    ys_set_status(res, YS_STATUS_UNAUTHORIZED);\n    ys_set_done(res);\n\n    return res;\n  }\n\n  ys_cookie_set_expires(c, ys_n_minutes_from_now(SESSION_TIMEOUT_MINUTES));\n  ys_set_cookie(res, c);\n\n  return res;\n}\n\nint main() {\n  session_store = ht_init(0);\n  user_store = ht_init(0);\n\n  ys_router_attr *attr = ys_router_attr_init();\n  ys_add_middleware_with_opts(attr, auth_middleware, \"^/$\", \"^/style.css$\",\n                              \"^/login$\", \"^/register$\");\n\n  ys_router *router = ys_router_init(attr);\n\n  ys_router_register(router, \"/register\", register_handler, YS_METHOD_POST);\n  ys_router_register(router, \"/login\", login_handler, YS_METHOD_POST);\n  ys_router_register(router, \"/logout\", logout_handler, YS_METHOD_POST);\n  ys_router_register(router, \"/data\", data_handler, YS_METHOD_GET);\n\n  ys_router_register(router, \"/\", index_handler, YS_METHOD_GET);\n  ys_router_register(router, \"/style.css\", css_handler, YS_METHOD_GET);\n\n  ys_server *server = ys_server_init(ys_server_attr_init(router));\n  ys_server_start(server);\n\n  return EXIT_SUCCESS;\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fexbotanical%2Fys","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fexbotanical%2Fys","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fexbotanical%2Fys/lists"}