惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

J
Java Code Geeks
MongoDB | Blog
MongoDB | Blog
B
Blog
博客园 - Franky
博客园 - 三生石上(FineUI控件)
A
About on SuperTechFans
N
Netflix TechBlog - Medium
MyScale Blog
MyScale Blog
阮一峰的网络日志
阮一峰的网络日志
美团技术团队
Vercel News
Vercel News
云风的 BLOG
云风的 BLOG
WordPress大学
WordPress大学
S
SegmentFault 最新的问题
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
宝玉的分享
宝玉的分享
小众软件
小众软件
P
Proofpoint News Feed
aimingoo的专栏
aimingoo的专栏
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
月光博客
月光博客
酷 壳 – CoolShell
酷 壳 – CoolShell
D
DataBreaches.Net
量子位

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
How I turned a Python function into a web app in one deco...
Kajetan Tuke · 2026-05-24 · via DEV Community

I've been building small utility tools for the web; JSON formatters, CSV processors, PDF text extractors, those kinds of things. The kind of tools where you have a problem, write a 50-line Python function to solve it, and it just works. Then, you want to share it with someone, but they can't be bothered (or don't know how) to set up the script and run it locally.

The obvious solution is "build a web app". The obvious problem is that building a frontend for every small script is a lot of overhead for something that should take an afternoon.

So I built something a little different.

The architecture

TL;DR: a Python function decorated with @tool() automatically becomes a web app. No React. No API endpoint. No routing.

Here's what a tool looks like on the Python side:

from nix_sdk import tool, TextResult

@tool(
    name="Simple JSON tool",
    description="Validate a JSON file - paste or upload!",
    slug="json-tool"
)
def json_formatter(
    json_input: str = Input(label="JSON input", placeholder="Paste JSON here..."),
    indent: int = Dropdown(label="Indent width", choices=[2, 4, 8], default=2),
    sort_keys: bool = Switch(label="Sort keys alphabetically"),
) -> TextResult:
    """Format and validate a JSON document."""
    import json
    parsed = json.loads(json_input)
    return TextResult(value=json.dumps(parsed, indent=indent, sort_keys=sort_keys))

Enter fullscreen mode Exit fullscreen mode

The @tool() decorator inspects the function's type hints and default values, and auto-generates a JSON Schema manifest. That manifest looks something like this:

{
"name": "Simple JSON tool",
"slug": "json-tool",
"input_schema": {
"type": "object",
"properties": {
"json_input": {
"type": "string",
"x-nix": { "widget": "textarea", "label": "JSON input" }
},
"indent": {
"type": "integer",
"enum": [2, 4, 8],
"x-nix": { "widget": "dropdown", "label": "Indent width" }
},
"sort_keys": {
"type": "boolean",
"x-nix": { "widget": "switch", "label": "Sort keys alphabetically" }
}
}
},
"output": { "type": "text" }
}

Separately, the Nix frontend shell reads that manifest and renders the form. Completely automatically.

What the shell does

The Next.js frontend has one dynamic route: /tools/[slug]. It fetches the manifest for that slug from the FastAPI backend, then renders the form using a custom renderer registry. Each x-nix.widget type maps to a Mantine component — textarea becomes a Textarea, dropdown becomes a Select, switch becomes a Switch, file becomes a file upload zone.

The same shell also renders a static landing page at /tools/[slug] with optional seo fields for metadata, H1, and FAQ content so that the content can be posted publicly. One Python file produces both the tool interface and the SEO-optimised landing page.

No React by you. No routing by you. No API wiring by you.

How execution works

The frontend submits the form to POST /api/tools/{slug}/run. FastAPI looks up the tool by slug, validates the input against the JSON Schema, and calls the Python function in a thread via asyncio.to_thread().

For long-running tools, there's a WebSocket path at /ws/tools/{slug}/run. The tool can call progress("Step 2 of 4: processing rows...") from inside the function and those messages stream to the UI in real time.

File uploads are handled as multipart form data. Files are ephemeral - held in temp storage during execution, then discarded. Nothing is persisted.

What's live now

I've used this to build nine tools that are live at nix.tech:

  • JSON Formatter & Validator and XML Formatter & Validator; validates with exact line/column error locations, finds all issues at once rather than stopping at the first
  • PDF Text Extractor; page-range selection, explicit scanned-PDF detection
  • CSV to JSON Converter; handles TSV too
  • CSV Deduplicator; column-level dedup with keep-first/keep-last options
  • Markdown to HTML; GFM extensions, fragment or full page output
  • Base64 Encoder/Decoder; handles files, not just strings
  • SEO Keyword Research; uses DataForSEO's API (to see how it works in these tools!) and provides search volume, CPC and ad competition without a Google Ads account
  • Alt Text Generator; AI-generated, SEO-focused alt text with optional site context

All the tools have free usage limits, without sign-ups. Files discarded after processing.

Tradeoffs

The manifest-driven approach has obvious limits. Complex multi-step flows are hard to express in a flat JSON Schema. Conditional field visibility requires extensions to the spec. Rich output types (e.g., interactive charts, side-by-side diffs) need custom renderer work.

For single-function utility tools - which is where I was testing for now — none of those limits are an issue. The upside is that adding a new tool is adding one Python file. The frontend, API endpoint, landing page, and form all derive from the manifest automatically.

Whether that tradeoff stays favourable at scale is a different question.

If any of the architecture is interesting to dig into further, happy to go into more detail in the comments!