First Steps with Jakarta Struts

Share this article

Just over a year ago, I started to learn how to use the Jakarta Struts J2EE Web application framework. During this journey, I realised that I found some aspects of Struts confusing at first, and that many other developers might well suffer the same difficulties. This article is an attempt to address that problem.

Many good books on the framework are now available, and the online documentation is quite mature for an open-source project; in spite of this, I have often had to refer to a wide variety of resources in order to build a good understanding of Struts. And there are still some areas in which the documentation is sparse.

However, this article isn’t an attempt to provide a definitive source of documentation. Rather, it’s intended to allow Struts newcomers to hit the ground running, to get a feel for what the framework is like, and understand what it can and can’t do.

Of the Struts example applications I’ve seen, most have been either too trivial or too complex, the complex ones having lots of external dependencies that must be resolved before the example can even be compiled and run. In this tutorial, I’ll attempt to hit a spot between these two extremes. I’ve deliberately created an application that has the minimum of external dependencies, so you should be able to actually run the code without too much difficulty! Everything you’ll need is open-source and can be downloaded for free — one of the advantages of J2EE.

The example application that we’re going to build is an online discussion forum, which I’ve given the rather generic name of “Web Forum”. I’ve chosen an application of this type because it should be familiar, and because you can create quite a lot of functionality without the application becoming too complex. Our Web Forum is fully functional and reasonably well-debugged; hopefully, it should be fun to learn how it all fits together.

It’s important to note that this code shouldn’t be considered an example of a production-quality J2EE system. In particular, the exception handling is extremely — and deliberately — simplistic. This isn’t a tutorial on persistence strategies, object-oriented design or Web application GUI design; it’s simply a vehicle for learning Struts. Undoubtedly, some aspects of what I’ve created here could be improved upon, but that’s not important right now. Let’s get started!

Ingredients

To start, let’s talk about the ingredients you’ll need to create the application. I developed the Web Forum using Oracle JDeveloper because I’m familiar with that IDE. You can use your Java tool of choice for writing the code.

Apart from the source code itself, here’s a list of the other elements you’ll need:

I also used the MySQL Control Centre to create the database and control the database server, although the MySQL command line can be used if that’s what you prefer. Note that, because this isn’t a MySQL tutorial, some familiarity with MySQL is assumed. As a minimum, you’ll need to know how to start and stop the database server, how to create a new database, and you’ll need the ability to run a provided SQL script to create tables.

Source Code Downloads

These are the files we’ll use through this tutorial.

To reduce the download sizes, I’ve split the Struts JAR files that go into WEB-INF/lib into a separate download. The pre-compiled WAR file is also available.

The Application

Let’s take a brief tour of the Web Forum’s functionality. I’d thought about some of this before I wrote a line of code, but other aspects became apparent as I was building the app. The fact that there are plenty of existing forums to serve as inspiration certainly helped!

Upon starting the application, the user is presented with a list of topics, and information about each topic, such as:

  • the date and time it was posted
  • the number of replies
  • the topic’s author

The 16 most recent topics are displayed, ordered from the newest to the oldest:

1471_topic

At this point, the user can click on a topic to view it, along with any follow-up replies:

1471_multitopics

The date and time of the post are displayed, as well as the authors’ names, the dates on which they registered with the system, and the number of posts they’ve made. The user can register if he or she is a new user, or log in if they’re an existing user. A user must be logged in before he or she can create a new topic or a new reply. The user can elect to log in automatically during registration, or at the Login screen:

1471_login

The auto-log in feature uses a cookie that expires after thirty days. During registration, the user must provide a unique user name, his or her real name (or screen alias) and a password. All of these fields are mandatory, apart from surname, and the user must confirm his or her chosen password by entering it twice:

1471_register

Upon successful registration, the user is taken back to the topics screen, on which he or she can create a new topic:

1471_newtopic

Both the subject and message fields are mandatory. Users are allowed to use the HTML <b>, <i> or <u> tags for basic text formatting (bold, italic or underline) within the message field. Once the user creates a new topic, he or she is taken back to the Topics screen.

The topic hyperlinks ensure that users can see which topics they’ve already read. The hyperlinks to topics that the user has already read are displayed using the browser’s visited link colour. However, each topic link contains an encoding of the number of replies that have been made to that topic. Therefore, new replies to a topic cause that topic’s hyperlink to be displayed using the browser’s unvisited colour. Thanks to Joel Spolsky for this brilliantly simple idea.

When viewing a topic, users can compose a new reply; again, the message field is mandatory and accepts basic formatting tags:

1471_newreply

The Persistence Layer

The persistence layer in the Web Forum application is very simple. There are two database tables: one for posts and another for users. The posts table stores topics and their replies. The users table stores the details of the registered users of the application. These tables are accessed using data access objects (DAOs), which are just Java objects that perform simple object-relational mapping. In other words, they take a Java object and persist its contents into a table, and vice-versa.

The DAOs have the appropriate SQL statements embedded into them, although this probably isn’t good practice: database administrators like such things to be externalised so they can be tuned. I have also created for all the DAOs an abstract superclass, which contains utility methods that obtain a JDBC connection from a data source and close database resources, etc.

This is the schema for the posts table:

1471_table1

The most interesting feature of this table is the ParentID column, which links replies back to their parent topic. The Subject column is null when a post is a reply and not a topic. I’m storing the number of replies to each topic in a ReplyCount column. Technically, this is redundant because it could be calculated, but I’m storing it because MySQL doesn’t currently support nested SQL SELECT statements.

The CreationDate column originally auto-updated, but I changed this because the date and time of the original topic were being updated to the current date and time whenever a reply was added to that topic.

Here’s the schema for the users table:

1471_table2

The MySQL SQL script that creates these tables is in src/sql/create_tables.sql. This script can be run from a query window in MySQL Control Centre. Note that it assumes the existence of a database named webforum. Also, because we’re not building the functionality to create new topics in this article, you’ll have to manually insert some test data so that you have some topics to display.

Business Object Layer

I didn’t have to do much thinking to come up with the business objects in the application: they’re pretty obvious. There are classes for individual posts and users, as well as collections of posts and users. The class model — excluding the Struts classes — is shown below:

1471_classmodel

Some of these classes, such as UserCookie, will be covered later in the series. As can be seen from the class model, a Posts class contains a collection of Post objects at runtime, and features methods for retrieving this collection and for adding a new post. The Post class itself has attributes that correspond to the columns in the posts table, and overloaded constructors that are invoked depending upon whether the post is a topic or a reply. Accessors and mutators (getters and setters) are not shown on the class model.

The Users class contains a collection of User objects at runtime, and features methods for retrieving this collection and for adding new users. In fact, I deprecated the getUsers method after I discovered that I’d coded it, but didn’t actually call it from anywhere! The User class has attributes that mirror the columns in the users table, as well as a convenient getDisplayName method that returns the user’s first names and surname with a space character in the middle.

Project Structure

The organisation of the source code folder tree is shown below:

1471_projectstructure

Java source code files go under src, and the Java package hierarchy is rooted at com.johntopley.webforum. The public_html folder corresponds to the root of the Web application.

I always put JSPs under pages because this allows them to be protected by the Web container using J2EE declarative security. Anything in public_html and its sub-folders really should be regarded as public.

I also like to separate the Struts configuration files into a config folder, although usually you’ll see them stored directly under WEB-INF.

The application entry point is public_html/index.jsp, which is declared as a welcome file in the web.xml Web application deployment descriptor. Let’s take a look at index.jsp:

<%@ taglib uri=http://jakarta.apache.org/struts/tags-logic  
   prefix="logic" %>  
<logic:redirect forward="ViewTopics" />

All this page does is transfer control to the Struts framework, because if we’re going to use a framework, we want to be using it as soon as possible. Struts ships with a number of JSP tag libraries (taglibs), and here we’re using the logic taglib. This handles the conditional generation of output text, looping over object collections and application flow management. In this case, the redirect tag performs an HTTP redirect to a Struts logical URL. More about that in a moment.

One thing to note about the Web Forum application is that we’re using the Servlet 2.3 specification syntax to reference the taglibs using URIs, rather than referring to TLD files in the web.xml file. This is documented in section 5.4.3 of The Struts User’s Guide.

The Heart of Struts

The heart of Struts is the config/struts-config.xml file. This file defines the flow of the application and tells Struts which classes to use for what. The Struts ActionServlet is a controller class that reads this file and receives all incoming requests for the Web application. The ActionServlet needs to be configured as a servlet in the web.xml file:

<servlet-name>action</servlet-name>  
 <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>  
 <init-param>  
   <param-name>config</param-name>  
   <param-value>/WEB-INF/config/struts-config.xml</param-value>  
 </init-param>  
 .  
 .  
 .

The main part of the struts-config.xml file covered by this article is:

<struts-config>  
 <global-forwards>  
   <forward name="ViewTopics" path="/ViewTopics.do"/>  
 </global-forwards>  
 
 <action-mappings>  
   <action  
     path="/ViewTopics"  
     type="com.johntopley.webforum.controller.action.ViewTopicsAction"  
     scope="request">  
     <forward  
       name="Topics"  
       path="/WEB-INF/pages/topics.jsp"  
     />  
   </action>  
 </action-mappings>  
</struts-config>

Struts introduces a layer of indirection into Web applications because it uses logical URLs. This means that the address you see in the browser’s address bar does not correspond to the physical location of that resource on the Web server. This allows developers to move resources around easily without breaking things. The Struts name for the association of a logical name with a resource is an ActionForward, often just called a Forward. The Struts configuration file contains a global-forwards section that allows the configuration of Forwards that are available throughout a Struts application. These are effectively the application’s entry points.

Another key Struts concept is the Action class. Actions are simply Java servlets, so anything that a servlet can do, an Action class can do. Actions are used to process requests for specific URLs. Generally, they should act as a thin layer around the business objects layer, which does the real work.

Actions are referred to by ActionMappings, which, again, are logical URLs. The Struts convention is that ActionMappings end with .do. The web.xml file needs to be configured so that the Struts ActionServlet is used to process any URL matching the pattern *.do, as shown here:

<servlet-mapping>  
 <servlet-name>action</servlet-name>  
 <url-pattern>*.do</url-pattern>  
</servlet-mapping>
Flow Of Events

Let’s recap where we’ve got to so far. The index.jsp page redirects to the ViewTopics global ActionForward, and this passes control to the /ViewTopics Action. Struts knows which Action class to invoke because the type attribute in the ActionMapping gives the fully-qualified name of the associated Java class, which must inherit from org.apache.struts.action.Action, and must have an execute method with the following signature:

public ActionForward execute(ActionMapping mapping,  
                            ActionForm form,  
                            HttpServletRequest request,  
                            HttpServletResponse response)  
 throws Exception

The mapping parameter is an ActionMapping object that represents the ActionMapping that invoked this Action. The form parameter is used if an HTML form is associated with the request. The request and response parameters highlight the fact that an Action class is just a specialisation of a servlet.

The Web Forum application uses a BaseAction abstract class that inherits from the org.apache.struts.action.Action class mentioned earlier. The other Action classes within the application inherit from BaseAction, so it serves as an extension point at which functionality common to all Action classes can be added if required. This should be considered good practice.

Important: Action classes should not use instance variables, as they are not thread-safe. State should be shared by storing values in the request, session, or servlet context objects, depending on their scope.

This is the execute method in ViewTopicsAction.java:

public ActionForward execute(ActionMapping mapping,  
                            ActionForm form,  
                            HttpServletRequest request,  
                            HttpServletResponse response)  
 throws Exception  
{  
 request.setAttribute(KeyConstants.POST_LIST_KEY,  
   new PostsDAO().getTopics());  
 
 return mapping.findForward(ForwardConstants.TOPICS_PAGE);  
}

A new PostsDAO object is instantiated and its getTopics method is called. This method uses the following SQL statement to query the posts table:

SELECT p.PostID, p.Subject, p.ReplyCount, p.UserID, p.CreationDate  
FROM Posts p  
WHERE p.ParentID = 0  
ORDER BY p.CreationDate DESC

The SQL WHERE clause ensures that only topics are selected — not replies. The getTopics method returns an instance of the Posts class, i.e., an ordered collection of Post objects. This instance is stored in the HTTP request under the key referred to by the KeyConstants.POST_LIST_KEY constant. The JSP that displays the list of topics will use this Posts object stored in the request. Finally, the findForward method of the ActionMapping class is invoked. This takes, as a String parameter, the name of a Struts Forward to pass control to. The ForwardConstants class contains all of the Forward names used within the Web Forum.

As well as global ActionForwards, Struts also has local ActionForwards. These are simply Forwards whose scope is local to a single ActionMapping. In other words, they are not globally visible within the application.

Important: Struts gives precedence to local ActionForwards over global ActionForwards.

A local Forward is used within the /ViewTopics ActionMapping to hold a reference to the JSP that displays the list of topics:

<forward name="Topics" path="/WEB-INF/pages/topics.jsp"/>

We finally have a physical path to a page! topics.jsp is included with the source code downloads for this article (linked above), so you can see the list of topics.

Building the Application Using Apache Ant

If you’re not familiar with the Apache Ant build tool, spend a few moments reading Apache Ant Demystified, which should get you up to speed quickly. The build file for the Web Forum application shouldn’t look too unfamiliar, although it does use some more of Ant’s built-in tasks. Another key difference is that, because this application is rather more than a simple “Hello World” app, we have to deal with the classpath. The good news is that Ant makes this easy too!

To compile the application, we need to have the J2EE libraries on the classpath. Fortunately, the Apache Tomcat Web container already comes with these libraries, so the question becomes one of referencing. Rather than hard-code Tomcat’s installation folder into our build file, let’s set up an operating system environment variable called TOMCAT_HOME that refers to the root of the Tomcat installation folder, e.g. C:Program FilesApache Software FoundationTomcat 5.0. The procedure for doing this is exactly the same as the one we used to create the ANT_HOME and JAVA_HOME environment variables in Apache Ant Demystified.

With that done, we can add a couple of property declarations to the top of our build.xml file:

<project name="webforum" default="dist" basedir=".">   
 <description>  
   Build file for the Struts WebForum application.  
 </description>  
 
 <property environment="env" />  
 <property name="tomcat.dir" location="${env.TOMCAT_HOME}" />

The first property declaration simply says that we want to use the identifier env to refer to operating system environment variables. The second property sets the location of tomcat.dir to whatever env.TOMCAT_HOME is. Thus, from now on, when we want to refer to Tomcat’s installation folder, we can simply use ${tomcat.dir}.

Later, we set up a property named j2ee.lib.dir that points to the location at which Tomcat keeps its J2EE libraries:

<property name="j2ee.lib.dir" location="${tomcat.dir}/common/lib" />

We now need to deal with setting up the classpath. Ant provides several ways to do this; we’re going to set up a path structure which we’ll assign the unique ID of classpath:

<path id="classpath">   
 <pathelement path="${j2ee.lib.dir}/servlet-api.jar" />  
 <fileset dir="${web.lib.dir}">  
   <include name="**/*.jar" />  
 </fileset>  
</path>

The snippet above shows that there are two elements within this path structure. The first element is an individual reference to the servlet-api.jar file, which is referenced using the pathelement tag. The second element is Ant’s fileset tag, which in our case has been set up to include all the JAR files within the folder represented by the ${web.lib.dir} property. To clarify, we’re setting up a classpath that includes Tomcat’s servlet-api.jar file and all the JAR files within public_html/WEB-INF/lib.

In order to use this classpath, the javac task within our build file’s compile target contains a nested classpath tag, which has a refid attribute set to the name of the path structure that we defined above:

<javac srcdir="${src.dir}" destdir="${classes.dir}">   
 <classpath refid="classpath" />  
</javac>

That’s how we make sure that the Java compiler can find all the libraries it needs to compile our application. The javadocs target also makes use of this classpath, which we defined once and can then re-use anywhere within the build file.

The next task is to package the compiled code into a WAR file (Web ARchive) so that it can be run by Tomcat. We create a dist target that looks like this:

<target name="dist" depends="compile"   
           description="Create the binary distribution.">  
 
 <delete dir="${dist.dir}" />  
 <mkdir dir="${dist.dir}" />  
 
 <war basedir="${web.src.dir}"  
         destfile="${dist.warfile}"  
         compress="true"  
         webxml="${web.src.dir}/WEB-INF/web.xml"  
         excludes="**/web.xml">  
        <classes dir="${classes.dir}" />  
 </war>  
</target>

Note that this target depends on the compile target, which makes sure that we always compile the code before we try to create a distribution in which it’s included! After deleting and recreating the output directory, we use Ant’s war task to create the WAR file. The basedir attribute points to a property that represents the public_html folder — the root of our application. The destfile attribute simply specifies the name of the output WAR file that’s to be created. We can choose whether or not to compress the file using — you guessed it — the compress attribute; the webxml attribute tells the war task where our Web application deployment descriptor is located. The use of this last attribute automatically ensures that the web.xml file is included in the WAR file. Thus, we use the excludes attribute to tell Ant not to generate a warning because it thinks we’re trying to include web.xml twice. Finally, a nested classes element tells the war task where our compiled classes are located.

If you run the dist target and then open /dist/WebForum.war using WinZip (or download the pre-compiled version from this site), you can see that the layout within the archive precisely mirrors the source code tree mentioned earlier. The compiled code has been placed within WEB-INF/classes and the Struts and MySQL JDBC JAR files are within WEB-INF/lib.

Running The Application Using Apache Tomcat

These instructions describe how to configure the Apache Tomcat Web container to use a MySQL data source. They’ve been tested using Tomcat 5.0.28 and MySQL 4.0.18, but should work with Tomcat 4 and other versions of MySQL, too. Note that TOMCAT_HOME refers to the root of the Tomcat installation folder, e.g. C:Program FilesApache Software FoundationTomcat 5.0.

Copy the MySQL JDBC driver JAR file to TOMCAT_HOME/common/lib.
Edit the TOMCAT_HOME/conf/server.xml file so that it includes a context definition for the Web Forum application. The main elements of the server.xml file are shown below:

<Server>   
 <Service>  
   <Engine>  
     <Host>  
       :  
       <Context>  
         <!-- Existing context definitions. -->  
       </Context>  
       :  
       <!--  
        | Paste Web Forum context definition (shown below) here.  
        +-->  
     </Host>  
   </Engine>  
 </Service>  
</Server>

Add the following context definition for the Web Forum application to the appropriate place in the server.xml file, substituting your MySQL webforum database user name and password where the italics indicate:

<!--   
| Begin WebForum context definition.  
+-->  
<Context path="/WebForum" docBase="WebForum"  
   reloadable="true">  
 
 <Logger className="org.apache.catalina.logger.FileLogger"  
     prefix="localhost_WebForum." suffix=".txt" timestamp="true"/>  
 
 <Resource name="jdbc/WebForumDS" auth="Container"  
     type="javax.sql.DataSource"/>  
 
 <ResourceParams name="jdbc/WebForumDS">  
   <parameter>  
     <name>factory</name>  
     <value>org.apache.commons.dbcp.BasicDataSourceFactory</value>  
   </parameter>  
 
   <!--  
    | The JDBC connection URL for connecting to your MySQL DB.  
    | The autoReconnect=true argument to the URL makes sure that the  
    | MySQL JDBC Driver will automatically reconnect if mysqld closed  
    | the connection. mysqld by default closes idle connections after  
    | 8 hours.  
    +-->  
   <parameter>  
     <name>url</name>  
     <value>jdbc:mysql://localhost:3306/webforum?  
         autoReconnect=true</value>  
   </parameter>  
 
   <!--  
    | MySQL username and password for DB connections.  
    +-->  
   <parameter>  
     <name>username</name>  
     <value>username</value>  
   </parameter>  
   <parameter>  
     <name>password</name>  
     <value>password</value>  
   </parameter>  
 
   <!--  
     | Class name for MySQL JDBC driver.  
     +-->  
   <parameter>  
     <name>driverClassName</name>  
     <value>com.mysql.jdbc.Driver</value>  
   </parameter>  
 
   <!--  
    | Maximum number of DB connections in pool. Make sure you  
    | configure your mysqld max_connections large enough to handle  
    | all of your DB connections. Set to 0 for no limit.  
    +-->  
   <parameter>  
     <name>maxActive</name>  
     <value>100</value>  
   </parameter>  
 
   <!--  
    | Maximum number of idle DB connections to retain in pool.  
    | Set to 0 for no limit.  
    +-->  
   <parameter>  
     <name>maxIdle</name>  
     <value>30</value>  
   </parameter>  
 
   <!--  
    | Maximum time to wait for a DB connection to become available  
    | in ms, in this example 10 seconds. An exception is thrown if  
    | this timeout is exceeded.  
    | Set to -1 to wait indefinitely.  
    +-->  
   <parameter>  
     <name>maxWait</name>  
     <value>10000</value>  
   </parameter>  
 </ResourceParams>  
</Context>  
<!--  
| End WebForum context definition.  
+-->

Next, copy the dist/WebForum.war we created earlier to TOMCAT_HOME/webapps. This archive will be automatically expanded and the Web Forum deployed the next time Tomcat is started.

After starting the MySQL and Tomcat servers, the application can be accessed in a Web browser using the URL: http://localhost:8080/WebForum/

We’ve covered a lot of important ground with this instalment. Next time, we’ll take a look at how the Topics page works, and move on from there. See you then!

Frequently Asked Questions (FAQs) about Struts

What is the difference between Struts 1 and Struts 2?

Struts 1 was the initial release of the Apache Struts framework, which was a revolutionary tool for building web applications. However, Struts 2, the successor to Struts 1, is more powerful and flexible. It incorporates many enhancements and new features such as a simpler, more modular architecture, improved tag features, better integration with Spring, and support for annotation-based configuration. Struts 2 also uses the WebWork framework which makes it more efficient and easier to use.

How does Struts work in Java?

Struts is a framework used for developing Java web applications. It uses the Model-View-Controller (MVC) design pattern to separate the application logic into three interconnected components. The Model represents the application’s data structure and business logic. The View is the user interface or the presentation of data. The Controller handles the user’s requests and orchestrates the model and view. Struts provide various components like Action classes, ActionForm beans, and configuration files to help in implementing MVC in a web application.

How can I install and set up Struts?

To install and set up Struts, you first need to download the latest version of the Struts library from the official Apache Struts website. Once downloaded, extract the zip file and add the Struts libraries to your Java project. You also need to set up the Struts configuration file (struts-config.xml) which is used to define the routing of the application. This file should be placed in the WEB-INF directory of your application.

What are the key components of Struts?

Struts framework is composed of several key components. These include the Controller Servlet, Action Classes, ActionForm Classes, struts-config.xml, and JSP pages. The Controller Servlet is the heart of the Struts framework that routes requests to appropriate handlers. Action Classes contain the business logic that is executed for each user request. ActionForm Classes are used to capture input from the form fields in the View. The struts-config.xml file is the configuration file that maps user requests to corresponding Action classes.

How can I handle form data in Struts?

In Struts, form data is typically handled using ActionForm classes. These classes encapsulate the form data and provide a way to validate and process the data. Each form in the application has a corresponding ActionForm class. When a form is submitted, the Struts framework automatically populates the corresponding ActionForm object with the form data. This object can then be used in the Action class to process the form data.

How does Struts handle validation?

Struts provides a robust validation framework that can be used to validate user input. This is done using validation rules defined in an XML file. When a form is submitted, the Struts framework checks these rules and if any rule is violated, it generates an error message which is then displayed on the form.

What is the role of the struts-config.xml file?

The struts-config.xml file is a crucial component of any Struts application. It is used to define the routing of the application. This file contains information about the Action classes, ActionForm classes, and their mappings. It also defines the navigation rules for the application.

How can I integrate Struts with other Java frameworks?

Struts can be easily integrated with other Java frameworks like Spring, Hibernate, etc. This is done by configuring the Struts framework to use the services provided by these other frameworks. For example, to integrate Struts with Spring, you need to configure the Struts Action classes as Spring beans in the Spring configuration file.

What are Struts tags?

Struts tags are custom JSP tags provided by the Struts framework. These tags simplify the development of JSP pages by providing functionality for form processing, data output, error handling, etc. Struts tags are grouped into several tag libraries including the HTML tag library, Bean tag library, Logic tag library, and Tiles tag library.

How can I handle exceptions in Struts?

Struts provides a centralized exception handling mechanism. You can define exception handling rules in the struts-config.xml file. When an exception occurs, the Struts framework checks these rules and forwards the request to an appropriate error handling page. This makes exception handling in Struts applications consistent and easy to manage.

John TopleyJohn Topley
View Author

John is a J2EE developer based in the UK, with a particular interest in user interface development and Web standards. John's Weblog and knowledge base is at http://www.johntopley.com/.

Share this article
Read Next
How to Use jQuery’s ajax() Function for Asynchronous HTTP Requests
How to Use jQuery’s ajax() Function for Asynchronous HTTP Requests
Aurelio De RosaMaria Antonietta Perna
Quick Tip: How to Align Column Rows with CSS Subgrid
Quick Tip: How to Align Column Rows with CSS Subgrid
Ralph Mason
15 Top Web Design Tools & Resources To Try in 2024
15 Top Web Design Tools & Resources To Try in 2024
SitePoint Sponsors
7 Simple Rules for Better Data Visualization
7 Simple Rules for Better Data Visualization
Mariia Merkulova
Cloudways Autonomous: Fully-Managed Scalable WordPress Hosting
Cloudways Autonomous: Fully-Managed Scalable WordPress Hosting
SitePoint Team
Best Programming Language for AI
Best Programming Language for AI
Lucero del Alba
Quick Tip: How to Add Gradient Effects and Patterns to Text
Quick Tip: How to Add Gradient Effects and Patterns to Text
Ralph Mason
Logging Made Easy: A Beginner’s Guide to Winston in Node.js
Logging Made Easy: A Beginner’s Guide to Winston in Node.js
Vultr
How to Optimize Website Content for Featured Snippets
How to Optimize Website Content for Featured Snippets
Dipen Visavadiya
Psychology and UX: Decoding the Science Behind User Clicks
Psychology and UX: Decoding the Science Behind User Clicks
Tanya Kumari
Build a Full-stack App with Node.js and htmx
Build a Full-stack App with Node.js and htmx
James Hibbard
Digital Transformation with AI: The Benefits and Challenges
Digital Transformation with AI: The Benefits and Challenges
Priyanka Prajapat
Quick Tip: Creating a Date Picker in React
Quick Tip: Creating a Date Picker in React
Dianne Pena
How to Create Interactive Animations Using React Spring
How to Create Interactive Animations Using React Spring
Yemi Ojedapo
10 Reasons to Love Google Docs
10 Reasons to Love Google Docs
Joshua KrausZain Zaidi
How to Use Magento 2 for International Ecommerce Success
How to Use Magento 2 for International Ecommerce Success
Mitul Patel
5 Exciting New JavaScript Features in 2024
5 Exciting New JavaScript Features in 2024
Olivia GibsonDarren Jones
Tools and Strategies for Efficient Web Project Management
Tools and Strategies for Efficient Web Project Management
Juliet Ofoegbu
Choosing the Best WordPress CRM Plugin for Your Business
Choosing the Best WordPress CRM Plugin for Your Business
Neve Wilkinson
ChatGPT Plugins for Marketing Success
ChatGPT Plugins for Marketing Success
Neil Jordan
Managing Static Files in Django: A Comprehensive Guide
Managing Static Files in Django: A Comprehensive Guide
Kabaki Antony
The Ultimate Guide to Choosing the Best React Website Builder
The Ultimate Guide to Choosing the Best React Website Builder
Dianne Pena
Exploring the Creative Power of CSS Filters and Blending
Exploring the Creative Power of CSS Filters and Blending
Joan Ayebola
How to Use WebSockets in Node.js to Create Real-time Apps
How to Use WebSockets in Node.js to Create Real-time Apps
Craig Buckler
Best Node.js Framework Choices for Modern App Development
Best Node.js Framework Choices for Modern App Development
Dianne Pena
SaaS Boilerplates: What They Are, And 10 of the Best
SaaS Boilerplates: What They Are, And 10 of the Best
Zain Zaidi
Understanding Cookies and Sessions in React
Understanding Cookies and Sessions in React
Blessing Ene Anyebe
Enhanced Internationalization (i18n) in Next.js 14
Enhanced Internationalization (i18n) in Next.js 14
Emmanuel Onyeyaforo
Essential React Native Performance Tips and Tricks
Essential React Native Performance Tips and Tricks
Shaik Mukthahar
How to Use Server-sent Events in Node.js
How to Use Server-sent Events in Node.js
Craig Buckler
Five Simple Ways to Boost a WooCommerce Site’s Performance
Five Simple Ways to Boost a WooCommerce Site’s Performance
Palash Ghosh
Elevate Your Online Store with Top WooCommerce Plugins
Elevate Your Online Store with Top WooCommerce Plugins
Dianne Pena
Unleash Your Website’s Potential: Top 5 SEO Tools of 2024
Unleash Your Website’s Potential: Top 5 SEO Tools of 2024
Dianne Pena
How to Build a Chat Interface using Gradio & Vultr Cloud GPU
How to Build a Chat Interface using Gradio & Vultr Cloud GPU
Vultr
Enhance Your React Apps with ShadCn Utilities and Components
Enhance Your React Apps with ShadCn Utilities and Components
David Jaja
10 Best Create React App Alternatives for Different Use Cases
10 Best Create React App Alternatives for Different Use Cases
Zain Zaidi
Control Lazy Load, Infinite Scroll and Animations in React
Control Lazy Load, Infinite Scroll and Animations in React
Blessing Ene Anyebe
Building a Research Assistant Tool with AI and JavaScript
Building a Research Assistant Tool with AI and JavaScript
Mahmud Adeleye
Understanding React useEffect
Understanding React useEffect
Dianne Pena
Web Design Trends to Watch in 2024
Web Design Trends to Watch in 2024
Juliet Ofoegbu
Building a 3D Card Flip Animation with CSS Houdini
Building a 3D Card Flip Animation with CSS Houdini
Fred Zugs
How to Use ChatGPT in an Unavailable Country
How to Use ChatGPT in an Unavailable Country
Dianne Pena
An Introduction to Node.js Multithreading
An Introduction to Node.js Multithreading
Craig Buckler
How to Boost WordPress Security and Protect Your SEO Ranking
How to Boost WordPress Security and Protect Your SEO Ranking
Jaya Iyer
Understanding How ChatGPT Maintains Context
Understanding How ChatGPT Maintains Context
Dianne Pena
Building Interactive Data Visualizations with D3.js and React
Building Interactive Data Visualizations with D3.js and React
Oluwabusayo Jacobs
JavaScript vs Python: Which One Should You Learn First?
JavaScript vs Python: Which One Should You Learn First?
Olivia GibsonDarren Jones
13 Best Books, Courses and Communities for Learning React
13 Best Books, Courses and Communities for Learning React
Zain Zaidi
5 jQuery.each() Function Examples
5 jQuery.each() Function Examples
Florian RapplJames Hibbard
Implementing User Authentication in React Apps with Appwrite
Implementing User Authentication in React Apps with Appwrite
Yemi Ojedapo
AI-Powered Search Engine With Milvus Vector Database on Vultr
AI-Powered Search Engine With Milvus Vector Database on Vultr
Vultr
Understanding Signals in Django
Understanding Signals in Django
Kabaki Antony
Why React Icons May Be the Only Icon Library You Need
Why React Icons May Be the Only Icon Library You Need
Zain Zaidi
View Transitions in Astro
View Transitions in Astro
Tamas Piros
Getting Started with Content Collections in Astro
Getting Started with Content Collections in Astro
Tamas Piros
What Does the Java Virtual Machine Do All Day?
What Does the Java Virtual Machine Do All Day?
Peter Kessler
Become a Freelance Web Developer on Fiverr: Ultimate Guide
Become a Freelance Web Developer on Fiverr: Ultimate Guide
Mayank Singh
Layouts in Astro
Layouts in Astro
Tamas Piros
.NET 8: Blazor Render Modes Explained
.NET 8: Blazor Render Modes Explained
Peter De Tender
Mastering Node CSV
Mastering Node CSV
Dianne Pena
A Beginner’s Guide to SvelteKit
A Beginner’s Guide to SvelteKit
Erik KückelheimSimon Holthausen
Brighten Up Your Astro Site with KwesForms and Rive
Brighten Up Your Astro Site with KwesForms and Rive
Paul Scanlon
Which Programming Language Should I Learn First in 2024?
Which Programming Language Should I Learn First in 2024?
Joel Falconer
Managing PHP Versions with Laravel Herd
Managing PHP Versions with Laravel Herd
Dianne Pena
Accelerating the Cloud: The Final Steps
Accelerating the Cloud: The Final Steps
Dave Neary
An Alphebetized List of MIME Types
An Alphebetized List of MIME Types
Dianne Pena
The Best PHP Frameworks for 2024
The Best PHP Frameworks for 2024
Claudio Ribeiro
11 Best WordPress Themes for Developers & Designers in 2024
11 Best WordPress Themes for Developers & Designers in 2024
SitePoint Sponsors
Top 9 Best WordPress AI Plugins of 2024
Top 9 Best WordPress AI Plugins of 2024
Dianne Pena
20+ Tools for Node.js Development in 2024
20+ Tools for Node.js Development in 2024
Dianne Pena
The Best Figma Plugins to Enhance Your Design Workflow in 2024
The Best Figma Plugins to Enhance Your Design Workflow in 2024
Dianne Pena
Harnessing the Power of Zenserp for Advanced Search Engine Parsing
Harnessing the Power of Zenserp for Advanced Search Engine Parsing
Christopher Collins
Build Your Own AI Tools in Python Using the OpenAI API
Build Your Own AI Tools in Python Using the OpenAI API
Zain Zaidi
The Best React Chart Libraries for Data Visualization in 2024
The Best React Chart Libraries for Data Visualization in 2024
Dianne Pena
7 Free AI Logo Generators to Get Started
7 Free AI Logo Generators to Get Started
Zain Zaidi
Turn Your Vue App into an Offline-ready Progressive Web App
Turn Your Vue App into an Offline-ready Progressive Web App
Imran Alam
Clean Architecture: Theming with Tailwind and CSS Variables
Clean Architecture: Theming with Tailwind and CSS Variables
Emmanuel Onyeyaforo
How to Analyze Large Text Datasets with LangChain and Python
How to Analyze Large Text Datasets with LangChain and Python
Matt Nikonorov
6 Techniques for Conditional Rendering in React, with Examples
6 Techniques for Conditional Rendering in React, with Examples
Yemi Ojedapo
Introducing STRICH: Barcode Scanning for Web Apps
Introducing STRICH: Barcode Scanning for Web Apps
Alex Suzuki
Using Nodemon and Watch in Node.js for Live Restarts
Using Nodemon and Watch in Node.js for Live Restarts
Craig Buckler
Task Automation and Debugging with AI-Powered Tools
Task Automation and Debugging with AI-Powered Tools
Timi Omoyeni
Quick Tip: Understanding React Tooltip
Quick Tip: Understanding React Tooltip
Dianne Pena
12 Outstanding AI Tools that Enhance Efficiency & Productivity
12 Outstanding AI Tools that Enhance Efficiency & Productivity
Ilija Sekulov
React Performance Optimization
React Performance Optimization
Blessing Ene Anyebe
Introducing Chatbots and Large Language Models (LLMs)
Introducing Chatbots and Large Language Models (LLMs)
Timi Omoyeni
Migrate to Ampere on OCI with Heterogeneous Kubernetes Clusters
Migrate to Ampere on OCI with Heterogeneous Kubernetes Clusters
Ampere Computing
Scale Your React App with Storybook and Chromatic
Scale Your React App with Storybook and Chromatic
Daine Mawer
10 Tips for Implementing Webflow On-page SEO
10 Tips for Implementing Webflow On-page SEO
Milan Vracar
Create Dynamic Web Experiences with Interactive SVG Animations
Create Dynamic Web Experiences with Interactive SVG Animations
Patricia Egyed
5 React Architecture Best Practices for 2024
5 React Architecture Best Practices for 2024
Sebastian Deutsch
How to Create Animated GIFs from GSAP Animations
How to Create Animated GIFs from GSAP Animations
Paul Scanlon
Aligning Teams for Effective User Onboarding Success
Aligning Teams for Effective User Onboarding Success
Himanshu Sharma
How to use the File System in Node.js
How to use the File System in Node.js
Craig Buckler
Laravel vs CodeIgniter: A Comprehensive Comparison
Laravel vs CodeIgniter: A Comprehensive Comparison
Dianne Pena
Essential Tips and Tricks for Coding HTML Emails
Essential Tips and Tricks for Coding HTML Emails
Rémi Parmentier
How to Create a Sortable and Filterable Table in React
How to Create a Sortable and Filterable Table in React
Ferenc Almasi
WooCommerce vs Wix: Which Is Best for Your Next Online Store
WooCommerce vs Wix: Which Is Best for Your Next Online Store
Priyanka Prajapati
GCC Guide for Ampere Processors
GCC Guide for Ampere Processors
John O’Neill
Navigating Data Management: Warehouses, Lakes and Lakehouses
Navigating Data Management: Warehouses, Lakes and Lakehouses
Leonid Chashnikov
Get the freshest news and resources for developers, designers and digital creators in your inbox each week