Skip to content

LittleX Tutorial for Beginners#

Welcome to the LittleX tutorial! This comprehensive guide is designed especially for beginners who want to learn Jaseci by building a simple Twitter-like application called LittleX. We'll break everything down into simple, digestible steps with clear explanations.


What is Jaseci?#

Jaseci is a powerful programming framework that makes building AI applications easier and more intuitive. Think of it as a comprehensive toolbox that enables you to:

  • Store data in a connected graph structure (like a social network)
  • Navigate through this graph to perform complex actions
  • Add AI capabilities without complex coding requirements

What We'll Build: LittleX#

LittleX is a simplified yet functional version of Twitter that allows users to:

  • Create accounts and personalized profiles
  • Post short messages (tweets) with rich content
  • Follow other users to build their network
  • View a personalized feed of posts from people they follow

Complete Implementation#

Just 200 lines of code to build a full social media platform!

LittleX Frontend

node Profile {
    has username: str = "";

    can update with update_profile entry;

    can get with get_profile entry;

    can follow with follow_request entry;

    can un_follow with un_follow_request entry;
}

obj TweetInfo {
    has username: str;
    has id: str;
    has content: str;
    has embedding: list;
    has likes: list;
    has comments: list;
}

node Tweet {
    has content: str;
    has embedding: list;
    has created_at: str = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S");

    can update with update_tweet exit;

    can delete with remove_tweet exit;

    can like_tweet with like_tweet entry;

    can remove_like with remove_like entry;

    can comment with comment_tweet entry;

    def get_info()-> TweetInfo;

    can get with load_feed entry;
}

node Comment {
    has content: str;

    can update with update_comment entry;

    can delete with remove_comment entry;
}

edge Follow {}

edge Like {}

edge Post {}

walker visit_profile {
    can visit_profile with `root entry;
}

walker update_profile(visit_profile) {
    has new_username: str;
}

walker get_profile(visit_profile) {}

walker load_user_profiles {
    obj __specs__ {
        static has auth: bool = False;
    }
    can load_profiles with `root entry;
}

walker follow_request {}

walker un_follow_request {}

walker create_tweet(visit_profile) {
    has content: str;

    can tweet with Profile entry;
}

walker update_tweet {
    has updated_content: str;
}

walker remove_tweet {}

walker like_tweet {}

walker remove_like {}

walker comment_tweet {
    has content: str;
}

walker update_comment {
    has updated_content: str;
}

walker remove_comment {}

walker load_feed(visit_profile) {
    has search_query: str = "";
    has results: list = [];

    can load with Profile entry;

}
impl Profile.update {
    self.username = visitor.new_username;
    report self;
}

impl Profile.get {
        follwers=[{"id": jid(i), "username": i.username} for i in [self-->(`?Profile)]];
        report {"user": self, "followers": follwers};
    }

impl Profile.follow{
        current_profile = [root-->(`?Profile)];
        current_profile[0] +>:Follow():+> self;
        report self;
    }

impl Profile.un_follow {
        current_profile = [root-->(`?Profile)];
        follow_edge = [edge current_profile[0] ->:Follow:-> self];
        del follow_edge[0];
        report self;
    }

impl Tweet.update {
        self.content = visitor.updated_content;
        report self;
    }

impl Tweet.delete {
        del self;
    }

impl Tweet.like_tweet {
        current_profile = [root-->(`?Profile)];
        self +>:Like():+> current_profile[0];
        report self;
    }

impl Tweet.remove_like {
        current_profile = [root-->(`?Profile)];
        like_edge = [edge self ->:Like:-> current_profile[0]];
        del like_edge[0];
        report self;
    }

impl Tweet.comment {
        current_profile = [root-->(`?Profile)];
        comment_node = current_profile[0] ++> Comment(content=visitor.content);
        _.perm_grant(comment_node[0], level="CONNECT");
        self ++> comment_node[0];
        report comment_node[0];
    }

impl Tweet.get_info {
        return TweetInfo(
            username=[self<-:Post:<-][0].username,
            id=jid(self),
            content=self.content,
            embedding=self.embedding,
            likes=[i.username for i in [self->:Like:->]],
            comments=[{"username": [i<--(`?Profile)][0].username, "id": jid(i), "content": i.content} for i in [self-->(`?Comment)]]
        );
    }

impl Tweet.get {
        tweet_info = self.get_info();
        similarity = search_tweets(visitor.search_query, tweet_info.content);
        visitor.results.append({"Tweet_Info": tweet_info, "similarity": similarity});
    }

impl Comment.update {
        self.content = visitor.updated_content;
        report self;
    }

impl Comment.delete {
        del self;
    }

impl visit_profile.visit_profile {
        visit [-->(`?Profile)] else {
            new_profile = here ++> Profile();
            _.perm_grant(new_profile[0], level="CONNECT");
            visit new_profile;
        }
    }

impl load_user_profiles.load_profiles {
        self.profiles: list = [];

        for user in NodeAnchor.Collection.find({"name": "Profile"}) {
            user_node = user.architype;
            self.profiles.append(
                {"name": user_node.username, "id": jid(user_node)}
            );
        }
        report self.profiles;
    }

impl create_tweet.tweet {
        embedding = sentence_transformer.encode(self.content).tolist();
        tweet_node = here +>:Post:+> Tweet(content=self.content, embedding=embedding);
        _.perm_grant(tweet_node[0], level="CONNECT");
        report tweet_node;
    }

impl load_feed.load {
        visit [-->(`?Tweet)];
        for user_node in [->:Follow:->(`?Profile)] {
            visit [user_node-->(`?Tweet)];
        }
        report self.results;
    }
test visit_profile {
    root spawn visit_profile();
    profile = [root --> (`?Profile)][0];
    check isinstance(profile,Profile);
}

test update_profile {
    root spawn update_profile(
        new_username = "test_user",
    );
    profile = [root --> (`?Profile)][0];
    check profile.username == "test_user";
}

test follow_request {
    followee = Profile("Sam");
    followee spawn follow_request();
    followee_profile = [root --> (`?Profile)->:Follow:->(`?Profile)][0];
    check followee_profile.username == "Sam";
}

test un_follow_request {
    followee = [root --> (`?Profile)->:Follow:->(`?Profile)][0];
    followee spawn un_follow_request();
    check len([root --> (`?Profile)->:Follow:->(`?Profile)]) == 0;
}

test create_tweet {
    root spawn create_tweet(
        content = "test_tweet",
    );
    test1 = [root --> (`?Profile) --> (`?Tweet)][0];
    check test1.content == "test_tweet";
}

test update_tweet {
    tweet1 = [root --> (`?Profile) --> (`?Tweet)][0];
    tweet1 spawn update_tweet(
        updated_content = "new_tweet",
    );
    check tweet1.content == "new_tweet";
}

test remove_tweet {
    tweet2 =  [root --> (`?Profile)--> (`?Tweet)][0];
    tweet2 spawn remove_tweet();
    check len([root --> (`?Profile) --> (`?Tweet)]) == 0;
}

test like_tweet {
    root spawn create_tweet(
        content = "test_like",
    );
    tweet1 = [root --> (`?Profile) --> (`?Tweet)][0];
    tweet1 spawn like_tweet();
    test1 = [tweet1 ->:Like:-> ][0];
    check test1.username == "test_user";
}

test remove_like {
    tweet1 = [root --> (`?Profile) --> (`?Tweet)][0];
    tweet1 spawn remove_like();
    check len([tweet1 ->:Like:-> ]) == 0;
}

test comment_tweet {
    tweet = [root --> (`?Profile) --> (`?Tweet)](?content == "test_like")[0];
    tweet spawn comment_tweet(
        content = "test_comment",
    );
    comment = [tweet --> (`?Comment)][0];
    check comment.content == "test_comment";
}

test update_comment {
    tweet = [root --> (`?Profile) --> (`?Tweet)](?content == "test_like")[0];
    comment = [tweet --> (`?Comment)][0];
    comment spawn update_comment(
        updated_content = "new_comment",
    );
    check comment.content == "new_comment";
}

test remove_comment {
    comment = [root --> (`?Profile) --> (`?Tweet) --> (`?Comment)][0];
    comment spawn remove_comment();
    check len([root --> (`?Profile) --> (`?Tweet) --> (`?Comment)]) == 0;
}

Getting Started#

Step 1: Install Jaseci#

First, let's install the required Jaseci libraries on your computer:

pip install jac_cloud

Step 2: Get the LittleX Code#

Clone the repository and navigate to the project directory:

git clone https://github.com/Jaseci-Labs/littleX.git
cd littleX

Step 3: Install Dependencies#

Install backend and frontend dependencies:

# Install backend dependencies
pip install -r littleX_BE/requirements.txt

# Install frontend dependencies
cd littleX_FE
npm install
cd ..

Understanding Jaclang's Building Blocks#

Jaclang has three main components that form the foundation of our LittleX application:

1. Nodes (The "Things")#

Nodes are objects that store data and represent entities in your application. In LittleX, we have:

  • User nodes → Store profile information
  • Post nodes → Store tweet content and metadata
  • Comment nodes → Store comments on tweets

Example: Simple User Node

node user {
    has username: str;
}

This code creates a user object with a username property.

2. Edges (The "Connections")#

Edges connect nodes to represent relationships between entities. In LittleX, we have:

  • Follow edges → User follows another user
  • Post edges → User created a post
  • Like edges → User liked a post

Example: Simple Follow Edge

edge Follow {}

This creates a "Follow" connection that links users together.

3. Walkers (The "Actions")#

Walkers are like functions that move through your graph and perform actions. They're what makes things happen in your application!

Example: Tweet Creation Walker

walker create_tweet(visit_profile) {
    has content: str;
    can tweet with Profile entry;
}

impl create_tweet.tweet {
    embedding = sentence_transformer.encode(self.content).tolist();
    tweet_node = here +>:Post:+> Tweet(content=self.content, embedding=embedding);
    _.perm_grant(tweet_node[0], level="CONNECT");
    report tweet_node;
}

This walker creates a new post with the provided content and links it to the current user.


Building LittleX Step by Step#

Now let's see how these pieces come together to build our social media application:

1. User Profile Creation#

When a new user registers, the system:

  1. Creates a new user node
  2. Stores their username and profile data
walker visit_profile {
    can visit_profile with `root entry;
}

impl visit_profile.visit_profile {
    visit [-->(`?Profile)] else {
        new_profile = here ++> Profile();
        _.perm_grant(new_profile[0], level="CONNECT");
        visit new_profile;
    }
}

2. Creating Posts#

After logging in, users can create and share posts:

walker create_tweet(visit_profile) {
    has content: str;
    can tweet with Profile entry;
}

impl create_tweet.tweet {
    embedding = sentence_transformer.encode(self.content).tolist();
    tweet_node = here +>:Post:+> Tweet(content=self.content, embedding=embedding);
    _.perm_grant(tweet_node[0], level="CONNECT");
    report tweet_node;
}

3. Following Users#

Users can build their network by following each other:

walker follow_request {}

impl Profile.follow {
    current_profile = [root-->(`?Profile)];
    current_profile[0] +>:Follow():+> self;
    report self;
}

4. Viewing Feed#

Display posts from people the user follows:

walker load_feed(visit_profile) {
    has search_query: str = "";
    has results: list = [];
    can load with Profile entry;
}

impl load_feed.load {
    visit [-->(`?Tweet)];
    for user_node in [->:Follow:->(`?Profile)] {
        visit [user_node-->(`?Tweet)];
    }
    report self.results;
}

Run LittleX Locally#

Let's get your application up and running:

Step 1: Start the Backend Server#

jac serve littleX_BE/littleX.jac

Success: Your backend server should now be running!

Step 2: Start the Frontend#

Open a new terminal and run:

cd littleX_FE
npm run dev

Success: Your frontend development server is now active!

Step 3: Use the Application#

  1. Open your browser to: http://localhost:5173

  2. Try these features:

  3. Creating a new account
  4. Posting some tweets
  5. Following other users
  6. Checking your personalized feed

Exploring the LittleX Code#

Let's examine some key components of the actual LittleX application:

The Profile Node#

node Profile {
    has username: str = "";

    can update with update_profile entry;
    can get with get_profile entry;
    can follow with follow_request entry;
    can un_follow with un_follow_request entry;
}

This node represents a user profile with username and comprehensive social media abilities.

The Tweet Node#

node Tweet {
    has content: str;
    has embedding: list;
    has created_at: str = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S");

    can update with update_tweet exit;
    can delete with remove_tweet exit;
    can like_tweet with like_tweet entry;
    can remove_like with remove_like entry;
    can comment with comment_tweet entry;

    def get_info() -> TweetInfo;
    can get with load_feed entry;
}

This node represents a tweet with rich metadata and full social interaction capabilities.

The Follow Ability#

impl Profile.follow {
    current_profile = [root-->(`?Profile)];
    current_profile[0] +>:Follow():+> self;
    report self;
}

This implementation creates a follow relationship between user profiles.


Adding AI Features#

Jaseci makes integrating AI into your application incredibly simple. Here's an example that summarizes tweets using AI:

import from mtllm.llms {Ollama}
glob llm = Ollama(host="http://127.0.0.1:11434", model_name="llama3.2:1b");

can 'Summarize latest trends, major events, and notable interactions from the recent tweets in one line.'
    summarise_tweets(tweets: list[str]) -> 'Summarisation': str by llm();

This code leverages a Llama language model to automatically summarize tweet collections.


Try These Exercises#

Ready to expand your skills? Try implementing these features:

  1. Add a like system for posts
  2. Create profile pages that display user-specific posts
  3. Implement user search functionality by username
  4. Add comment threading for deeper conversations

Conclusion#

Congratulations! You've successfully learned how to build a complete social media application with Jaseci. You now understand how:

  • Nodes store and organize your application data
  • Edges create meaningful relationships between entities
  • Walkers perform complex actions and business logic

Jaseci's graph-based approach makes it perfect for social networks and other applications where connections between data are crucial for functionality.


Next Steps#

Ready to dive deeper? Explore these resources:

Happy coding with Jaseci! 🚀